@manta-eu/react 0.2.0 → 0.2.1

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 (235) hide show
  1. package/dist/components/channel-icon/index.d.ts +1 -0
  2. package/dist/components/channel-icon/index.d.ts.map +1 -1
  3. package/dist/components/channel-icon/index.js +1 -1
  4. package/dist/components/channel-icon/index.js.map +1 -1
  5. package/dist/components/planning/ActioningView.d.ts +1 -2
  6. package/dist/components/planning/ActioningView.d.ts.map +1 -1
  7. package/dist/components/planning/ActioningView.js +19 -44
  8. package/dist/components/planning/ActioningView.js.map +1 -1
  9. package/dist/components/planning/ManualActionCard.d.ts +4 -0
  10. package/dist/components/planning/ManualActionCard.d.ts.map +1 -1
  11. package/dist/components/planning/ManualActionCard.js +7 -5
  12. package/dist/components/planning/ManualActionCard.js.map +1 -1
  13. package/dist/components/planning/ManualActionList.d.ts +4 -0
  14. package/dist/components/planning/ManualActionList.d.ts.map +1 -1
  15. package/dist/components/planning/ManualActionList.js +8 -2
  16. package/dist/components/planning/ManualActionList.js.map +1 -1
  17. package/dist/components/planning/actioning-view-utils.d.ts +9 -0
  18. package/dist/components/planning/actioning-view-utils.d.ts.map +1 -1
  19. package/dist/components/planning/actioning-view-utils.js +19 -1
  20. package/dist/components/planning/actioning-view-utils.js.map +1 -1
  21. package/dist/components/tooltip/index.d.ts +7 -0
  22. package/dist/components/tooltip/index.d.ts.map +1 -1
  23. package/dist/components/tooltip/index.js +10 -1
  24. package/dist/components/tooltip/index.js.map +1 -1
  25. package/dist/globals.css +19 -6
  26. package/dist/graph/entity-link.d.ts +14 -0
  27. package/dist/graph/entity-link.d.ts.map +1 -0
  28. package/dist/graph/entity-link.js +12 -0
  29. package/dist/graph/entity-link.js.map +1 -0
  30. package/dist/graph/graph-list-utils.d.ts +1 -0
  31. package/dist/graph/graph-list-utils.d.ts.map +1 -1
  32. package/dist/graph/graph-list-utils.js +1 -0
  33. package/dist/graph/graph-list-utils.js.map +1 -1
  34. package/dist/graph/graph-model.d.ts.map +1 -1
  35. package/dist/graph/graph-model.js +14 -5
  36. package/dist/graph/graph-model.js.map +1 -1
  37. package/dist/graph/nodes/BrandAssetAnalysisView.d.ts.map +1 -1
  38. package/dist/graph/nodes/BrandAssetAnalysisView.js +2 -1
  39. package/dist/graph/nodes/BrandAssetAnalysisView.js.map +1 -1
  40. package/dist/graph/nodes/BrandFontSuggestionView.d.ts.map +1 -1
  41. package/dist/graph/nodes/BrandFontSuggestionView.js +2 -1
  42. package/dist/graph/nodes/BrandFontSuggestionView.js.map +1 -1
  43. package/dist/graph/nodes/BrandGuidelinesView.js +1 -1
  44. package/dist/graph/nodes/BrandGuidelinesView.js.map +1 -1
  45. package/dist/graph/nodes/NodePropertyTable.d.ts +4 -2
  46. package/dist/graph/nodes/NodePropertyTable.d.ts.map +1 -1
  47. package/dist/graph/nodes/NodePropertyTable.js +12 -59
  48. package/dist/graph/nodes/NodePropertyTable.js.map +1 -1
  49. package/dist/graph/nodes/node-property-row.d.ts +30 -0
  50. package/dist/graph/nodes/node-property-row.d.ts.map +1 -0
  51. package/dist/graph/nodes/node-property-row.js +35 -0
  52. package/dist/graph/nodes/node-property-row.js.map +1 -0
  53. package/dist/graph/property-formatters.d.ts +1 -0
  54. package/dist/graph/property-formatters.d.ts.map +1 -1
  55. package/dist/graph/property-formatters.js +4 -1
  56. package/dist/graph/property-formatters.js.map +1 -1
  57. package/dist/graph/special-nodes/AttributeList.d.ts +27 -0
  58. package/dist/graph/special-nodes/AttributeList.d.ts.map +1 -0
  59. package/dist/graph/special-nodes/AttributeList.js +57 -0
  60. package/dist/graph/special-nodes/AttributeList.js.map +1 -0
  61. package/dist/graph/special-nodes/EntityPreview.d.ts +5 -0
  62. package/dist/graph/special-nodes/EntityPreview.d.ts.map +1 -0
  63. package/dist/graph/special-nodes/EntityPreview.js +19 -0
  64. package/dist/graph/special-nodes/EntityPreview.js.map +1 -0
  65. package/dist/graph/special-nodes/EntityValueChip.d.ts +11 -0
  66. package/dist/graph/special-nodes/EntityValueChip.d.ts.map +1 -0
  67. package/dist/graph/special-nodes/EntityValueChip.js +40 -0
  68. package/dist/graph/special-nodes/EntityValueChip.js.map +1 -0
  69. package/dist/graph/special-nodes/GenericView.d.ts +2 -1
  70. package/dist/graph/special-nodes/GenericView.d.ts.map +1 -1
  71. package/dist/graph/special-nodes/GenericView.js +23 -69
  72. package/dist/graph/special-nodes/GenericView.js.map +1 -1
  73. package/dist/graph/special-nodes/card-facts.d.ts +52 -0
  74. package/dist/graph/special-nodes/card-facts.d.ts.map +1 -0
  75. package/dist/graph/special-nodes/card-facts.js +189 -0
  76. package/dist/graph/special-nodes/card-facts.js.map +1 -0
  77. package/dist/graph/special-nodes/property-icons.d.ts +9 -0
  78. package/dist/graph/special-nodes/property-icons.d.ts.map +1 -0
  79. package/dist/graph/special-nodes/property-icons.js +39 -0
  80. package/dist/graph/special-nodes/property-icons.js.map +1 -0
  81. package/dist/graph/status-utils.d.ts +16 -0
  82. package/dist/graph/status-utils.d.ts.map +1 -1
  83. package/dist/graph/status-utils.js +35 -0
  84. package/dist/graph/status-utils.js.map +1 -1
  85. package/dist/graph/types.d.ts +5 -0
  86. package/dist/graph/types.d.ts.map +1 -1
  87. package/dist/hooks/use-approve-anyway.d.ts +12 -0
  88. package/dist/hooks/use-approve-anyway.d.ts.map +1 -0
  89. package/dist/hooks/use-approve-anyway.js +81 -0
  90. package/dist/hooks/use-approve-anyway.js.map +1 -0
  91. package/dist/hooks/use-connection-problems.d.ts.map +1 -1
  92. package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
  93. package/dist/ldo-ui.d.ts +2 -1
  94. package/dist/ldo-ui.d.ts.map +1 -1
  95. package/dist/ldo-ui.js +1 -0
  96. package/dist/ldo-ui.js.map +1 -1
  97. package/dist/lib/manual-actions.d.ts +2 -1
  98. package/dist/lib/manual-actions.d.ts.map +1 -1
  99. package/dist/lib/manual-actions.js +3 -2
  100. package/dist/lib/manual-actions.js.map +1 -1
  101. package/dist/providers/ActiveChatProvider.js +3 -1
  102. package/dist/providers/ActiveChatProvider.js.map +1 -1
  103. package/dist/providers/ChatManagerProvider.d.ts.map +1 -1
  104. package/dist/providers/ChatManagerProvider.js +3 -1
  105. package/dist/providers/ChatManagerProvider.js.map +1 -1
  106. package/dist/providers/chat-socket-auth.d.ts +7 -2
  107. package/dist/providers/chat-socket-auth.d.ts.map +1 -1
  108. package/dist/providers/chat-socket-auth.js +87 -16
  109. package/dist/providers/chat-socket-auth.js.map +1 -1
  110. package/dist/providers/index.d.ts.map +1 -1
  111. package/dist/providers/index.js +8 -1
  112. package/dist/providers/index.js.map +1 -1
  113. package/dist/routes/activity/detail/ActivityChanges.d.ts +2 -0
  114. package/dist/routes/activity/detail/ActivityChanges.d.ts.map +1 -1
  115. package/dist/routes/activity/detail/ActivityChanges.js +8 -7
  116. package/dist/routes/activity/detail/ActivityChanges.js.map +1 -1
  117. package/dist/routes/activity/detail/ActivityDetail.d.ts +1 -0
  118. package/dist/routes/activity/detail/ActivityDetail.d.ts.map +1 -1
  119. package/dist/routes/activity/detail/ActivityDetail.js +5 -4
  120. package/dist/routes/activity/detail/ActivityDetail.js.map +1 -1
  121. package/dist/routes/activity/detail/ActivityStepsRail.d.ts +1 -0
  122. package/dist/routes/activity/detail/ActivityStepsRail.d.ts.map +1 -1
  123. package/dist/routes/activity/detail/ActivityStepsRail.js +3 -2
  124. package/dist/routes/activity/detail/ActivityStepsRail.js.map +1 -1
  125. package/dist/routes/activity/detail/ActivityTimeline.js +2 -2
  126. package/dist/routes/activity/detail/ActivityTimeline.js.map +1 -1
  127. package/dist/routes/activity/detail/NoChangesMessage.d.ts +6 -0
  128. package/dist/routes/activity/detail/NoChangesMessage.d.ts.map +1 -0
  129. package/dist/routes/activity/detail/NoChangesMessage.js +10 -0
  130. package/dist/routes/activity/detail/NoChangesMessage.js.map +1 -0
  131. package/dist/routes/data-explorer/components/RawTriples.js +1 -1
  132. package/dist/routes/data-explorer/components/RawTriples.js.map +1 -1
  133. package/dist/routes/data-explorer/components/RecentTypes.js +1 -1
  134. package/dist/routes/data-explorer/components/RecentTypes.js.map +1 -1
  135. package/dist/routes/data-explorer/components/class-detail/Instances.js +1 -1
  136. package/dist/routes/data-explorer/components/class-detail/Instances.js.map +1 -1
  137. package/dist/routes/data-explorer/components/namespace-detail/NamespaceClassRow.js +1 -1
  138. package/dist/routes/data-explorer/components/namespace-detail/NamespaceClassRow.js.map +1 -1
  139. package/dist/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.js +1 -1
  140. package/dist/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.js.map +1 -1
  141. package/dist/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.js +1 -1
  142. package/dist/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.js.map +1 -1
  143. package/dist/routes/data-explorer/components/record-detail/ChatPreview.js +2 -2
  144. package/dist/routes/data-explorer/components/record-detail/ChatPreview.js.map +1 -1
  145. package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.d.ts.map +1 -1
  146. package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.js +13 -6
  147. package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.js.map +1 -1
  148. package/dist/routes/integrations/components/connect-forms/data-connection-fields.d.ts +3 -0
  149. package/dist/routes/integrations/components/connect-forms/data-connection-fields.d.ts.map +1 -1
  150. package/dist/routes/integrations/components/connect-forms/data-connection-fields.js +9 -2
  151. package/dist/routes/integrations/components/connect-forms/data-connection-fields.js.map +1 -1
  152. package/dist/routes/plans/components/ActionPlanPage.d.ts.map +1 -1
  153. package/dist/routes/plans/components/ActionPlanPage.js +4 -7
  154. package/dist/routes/plans/components/ActionPlanPage.js.map +1 -1
  155. package/dist/routes/plans/components/PlanDetailRouteContent.d.ts.map +1 -1
  156. package/dist/routes/plans/components/PlanDetailRouteContent.js +2 -1
  157. package/dist/routes/plans/components/PlanDetailRouteContent.js.map +1 -1
  158. package/package.json +12 -7
  159. package/src/app-subpaths.json +1 -0
  160. package/src/components/channel-icon/index.tsx +1 -1
  161. package/src/components/planning/ActioningView.tsx +62 -70
  162. package/src/components/planning/ManualActionCard.tsx +66 -14
  163. package/src/components/planning/ManualActionList.tsx +30 -11
  164. package/src/components/planning/actioning-view-utils.ts +27 -1
  165. package/src/components/tooltip/index.tsx +35 -2
  166. package/src/graph/entity-link.tsx +20 -0
  167. package/src/graph/graph-list-utils.ts +2 -0
  168. package/src/graph/graph-model.ts +15 -6
  169. package/src/graph/nodes/BrandAssetAnalysisView.tsx +3 -1
  170. package/src/graph/nodes/BrandFontSuggestionView.tsx +7 -1
  171. package/src/graph/nodes/BrandGuidelinesView.tsx +1 -1
  172. package/src/graph/nodes/NodePropertyTable.tsx +32 -143
  173. package/src/graph/nodes/node-property-row.tsx +154 -0
  174. package/src/graph/property-formatters.ts +5 -1
  175. package/src/graph/special-nodes/AttributeList.tsx +177 -0
  176. package/src/graph/special-nodes/EntityPreview.tsx +27 -0
  177. package/src/graph/special-nodes/EntityValueChip.tsx +85 -0
  178. package/src/graph/special-nodes/GenericView.tsx +49 -139
  179. package/src/graph/special-nodes/card-facts.ts +278 -0
  180. package/src/graph/special-nodes/property-icons.ts +59 -0
  181. package/src/graph/status-utils.ts +40 -0
  182. package/src/graph/types.ts +4 -0
  183. package/src/hooks/use-approve-anyway.ts +100 -0
  184. package/src/ldo-ui.ts +8 -0
  185. package/src/lib/manual-actions.ts +3 -2
  186. package/src/providers/ActiveChatProvider.tsx +3 -1
  187. package/src/providers/ChatManagerProvider.tsx +3 -1
  188. package/src/providers/chat-socket-auth.ts +95 -16
  189. package/src/providers/index.tsx +16 -1
  190. package/src/routes/activity/detail/ActivityChanges.tsx +21 -5
  191. package/src/routes/activity/detail/ActivityDetail.tsx +24 -5
  192. package/src/routes/activity/detail/ActivityStepsRail.tsx +4 -2
  193. package/src/routes/activity/detail/ActivityTimeline.tsx +2 -2
  194. package/src/routes/activity/detail/NoChangesMessage.tsx +20 -0
  195. package/src/routes/data-explorer/components/RawTriples.tsx +1 -1
  196. package/src/routes/data-explorer/components/RecentTypes.tsx +1 -1
  197. package/src/routes/data-explorer/components/class-detail/Instances.tsx +1 -1
  198. package/src/routes/data-explorer/components/namespace-detail/NamespaceClassRow.tsx +1 -1
  199. package/src/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.tsx +1 -1
  200. package/src/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.tsx +1 -1
  201. package/src/routes/data-explorer/components/record-detail/ChatPreview.tsx +2 -2
  202. package/src/routes/integrations/components/connect-forms/DataConnectionConnectForm.tsx +38 -12
  203. package/src/routes/integrations/components/connect-forms/data-connection-fields.ts +11 -2
  204. package/src/routes/plans/components/ActionPlanPage.tsx +9 -8
  205. package/src/routes/plans/components/PlanDetailRouteContent.tsx +4 -3
  206. package/dist/components/planning/ExecutionActivityRow.d.ts +0 -9
  207. package/dist/components/planning/ExecutionActivityRow.d.ts.map +0 -1
  208. package/dist/components/planning/ExecutionActivityRow.js +0 -53
  209. package/dist/components/planning/ExecutionActivityRow.js.map +0 -1
  210. package/dist/components/planning/ExecutionSectionItem.d.ts +0 -20
  211. package/dist/components/planning/ExecutionSectionItem.d.ts.map +0 -1
  212. package/dist/components/planning/ExecutionSectionItem.js +0 -66
  213. package/dist/components/planning/ExecutionSectionItem.js.map +0 -1
  214. package/dist/components/planning/ExecutionSections.d.ts +0 -16
  215. package/dist/components/planning/ExecutionSections.d.ts.map +0 -1
  216. package/dist/components/planning/ExecutionSections.js +0 -32
  217. package/dist/components/planning/ExecutionSections.js.map +0 -1
  218. package/dist/components/planning/action-plan-icons.d.ts +0 -3
  219. package/dist/components/planning/action-plan-icons.d.ts.map +0 -1
  220. package/dist/components/planning/action-plan-icons.js +0 -25
  221. package/dist/components/planning/action-plan-icons.js.map +0 -1
  222. package/dist/components/planning/execution-section-model.d.ts +0 -24
  223. package/dist/components/planning/execution-section-model.d.ts.map +0 -1
  224. package/dist/components/planning/execution-section-model.js +0 -150
  225. package/dist/components/planning/execution-section-model.js.map +0 -1
  226. package/dist/routes/data-explorer/components/TruncateTooltip.d.ts +0 -7
  227. package/dist/routes/data-explorer/components/TruncateTooltip.d.ts.map +0 -1
  228. package/dist/routes/data-explorer/components/TruncateTooltip.js +0 -9
  229. package/dist/routes/data-explorer/components/TruncateTooltip.js.map +0 -1
  230. package/src/components/planning/ExecutionActivityRow.tsx +0 -89
  231. package/src/components/planning/ExecutionSectionItem.tsx +0 -184
  232. package/src/components/planning/ExecutionSections.tsx +0 -97
  233. package/src/components/planning/action-plan-icons.tsx +0 -27
  234. package/src/components/planning/execution-section-model.ts +0 -190
  235. package/src/routes/data-explorer/components/TruncateTooltip.tsx +0 -25
@@ -0,0 +1,177 @@
1
+ import { isFullIri } from "@manta-eu/client/shared/rdf-namespaces";
2
+ import { isPresent } from "@manta-eu/lib/present";
3
+ import { Search } from "lucide-react";
4
+ import { useMemo, useState } from "react";
5
+ import type { PreviewFieldHints } from "../graph-list-utils";
6
+ import { NodePropertyExpander, NodePropertyList, NodePropertyRow, PropertyValue } from "../nodes/node-property-row";
7
+ import { ontologyTermIrisForProperties, withOntologyLabels } from "../ontology-labels";
8
+ import type { NodeProperty, NodeRelation } from "../types";
9
+ import { buildCardFacts, type CardFact, isAttributeSearchable, searchFacts, visibleChips } from "./card-facts";
10
+ import { EntityValueChip } from "./EntityValueChip";
11
+ import { ChannelIcon, channelDisplayName } from "#components/channel-icon/index";
12
+ import { useOntologyTerms } from "#hooks/use-ontology-terms";
13
+ import { cn } from "#style";
14
+
15
+ export type UseAttributeFactsArgs = {
16
+ properties: NodeProperty[];
17
+ relations?: NodeRelation[];
18
+ jsonLd: Record<string, unknown>;
19
+ title?: string;
20
+ type?: string;
21
+ prefixLabels?: boolean;
22
+ previewHints?: PreviewFieldHints;
23
+ // Applied after ontology labels resolve, so a filter can match on the label too. Keep it stable (useCallback).
24
+ include?: (property: NodeProperty) => boolean;
25
+ };
26
+
27
+ export function useAttributeFacts(args: UseAttributeFactsArgs): { facts: CardFact[]; previewCount: number } {
28
+ const { properties, jsonLd, title, type, prefixLabels, previewHints, include } = args;
29
+
30
+ const relations = useMemo(() => args.relations ?? [], [args.relations]);
31
+
32
+ const terms = useOntologyTerms(
33
+ useMemo(
34
+ () => [
35
+ ...ontologyTermIrisForProperties(properties),
36
+ ...relations.flatMap((relation) => [relation.predicate, relation.target]).filter(isFullIri),
37
+ ],
38
+ [properties, relations],
39
+ ),
40
+ );
41
+
42
+ return useMemo(() => {
43
+ const labeled = withOntologyLabels(properties, terms);
44
+
45
+ return buildCardFacts({
46
+ properties: include ? labeled.filter(include) : labeled,
47
+ relations,
48
+ jsonLd,
49
+ terms,
50
+ title,
51
+ type,
52
+ prefixLabels,
53
+ previewHints,
54
+ });
55
+ }, [properties, terms, include, relations, jsonLd, title, type, prefixLabels, previewHints]);
56
+ }
57
+
58
+ type EntityChipsProps = { iris: string[]; color?: string; expanded: boolean };
59
+
60
+ function EntityChips(props: EntityChipsProps) {
61
+ const { iris, color, expanded } = props;
62
+
63
+ const { shown, hiddenCount } = visibleChips(iris, expanded);
64
+
65
+ return (
66
+ <span className="flex min-w-0 flex-wrap items-center gap-1">
67
+ {shown.map((iri) => (
68
+ <EntityValueChip key={iri} iri={iri} color={color} />
69
+ ))}
70
+ {hiddenCount > 0 && <span className="typography-tiny text-muted-foreground">+{hiddenCount}</span>}
71
+ </span>
72
+ );
73
+ }
74
+
75
+ export type AttributeListProps = {
76
+ facts: CardFact[];
77
+ previewCount: number;
78
+ searchClassName?: string;
79
+ bodyClassName?: string;
80
+ // Lists every attribute with no preview, so a card that promises the whole record keeps that promise.
81
+ showAll?: boolean;
82
+ defaultExpanded?: boolean;
83
+ };
84
+
85
+ export function AttributeList(props: AttributeListProps) {
86
+ const { facts, previewCount, searchClassName, bodyClassName, showAll = false, defaultExpanded = false } = props;
87
+
88
+ const [expandedByUser, setExpanded] = useState(defaultExpanded);
89
+ const expanded = showAll || expandedByUser;
90
+ const [query, setQuery] = useState("");
91
+
92
+ const searchable = isAttributeSearchable(expanded, facts.length);
93
+ const searching = searchable && isPresent(query.trim());
94
+ const matchingFacts = useMemo(
95
+ () => (searching ? searchFacts(facts, query.trim()) : facts),
96
+ [searching, facts, query],
97
+ );
98
+
99
+ const visibleFacts = searching || expanded ? matchingFacts : facts.slice(0, previewCount);
100
+ const hasHiddenChips = visibleFacts.some((fact) => visibleChips(fact.entityIris, expanded).hiddenCount > 0);
101
+ const hiddenCount = facts.length - previewCount;
102
+
103
+ if (!isPresent(facts)) return null;
104
+
105
+ return (
106
+ <div className="flex flex-col">
107
+ {searchable && (
108
+ <label
109
+ className={cn(
110
+ "nodrag nopan flex items-center gap-2 border-b border-muted-border py-2 text-muted-foreground",
111
+ searchClassName,
112
+ )}
113
+ >
114
+ <Search aria-hidden className="size-3.5 shrink-0" />
115
+ <input
116
+ type="search"
117
+ value={query}
118
+ onChange={(event) => setQuery(event.target.value)}
119
+ onKeyDown={(event) => event.stopPropagation()}
120
+ placeholder="Search attributes…"
121
+ aria-label="Search attributes"
122
+ className="typography-tiny min-w-0 flex-1 bg-transparent text-default-foreground outline-none placeholder:text-muted-foreground"
123
+ />
124
+ </label>
125
+ )}
126
+
127
+ <div className={cn("flex flex-col", bodyClassName)}>
128
+ <NodePropertyList>
129
+ {visibleFacts.map(({ key, label, value, display, icon, entityIris, entityColor, highlighted, channel }) => (
130
+ <NodePropertyRow
131
+ key={key}
132
+ label={label}
133
+ icon={channel ? undefined : icon}
134
+ labelPrefix={
135
+ channel && (
136
+ <span
137
+ role="img"
138
+ aria-label={channelDisplayName[channel]}
139
+ title={channelDisplayName[channel]}
140
+ className="shrink-0 self-center"
141
+ >
142
+ <ChannelIcon channel={channel} iconClassName="size-3" />
143
+ </span>
144
+ )
145
+ }
146
+ overflow={isPresent(entityIris) ? "wrap" : expanded && display.kind === "prose" ? "clamp" : "truncate"}
147
+ valueTitle={value}
148
+ divided={false}
149
+ wideLabel
150
+ emphasized={highlighted}
151
+ valueClassName={cn("tabular-nums", highlighted && "text-primary")}
152
+ >
153
+ {isPresent(entityIris) ? (
154
+ <EntityChips iris={entityIris} color={entityColor} expanded={expanded} />
155
+ ) : (
156
+ <PropertyValue display={display} />
157
+ )}
158
+ </NodePropertyRow>
159
+ ))}
160
+ </NodePropertyList>
161
+ {searching && !isPresent(visibleFacts) && (
162
+ <span className="typography-tiny py-1 text-muted-foreground">No matching attributes.</span>
163
+ )}
164
+ {!showAll && (hiddenCount > 0 || hasHiddenChips || expanded) && (
165
+ <NodePropertyExpander
166
+ expanded={expanded}
167
+ collapsedLabel={hiddenCount > 0 ? `Show all ${facts.length}` : "Show all"}
168
+ onToggle={() => {
169
+ setExpanded((value) => !value);
170
+ setQuery("");
171
+ }}
172
+ />
173
+ )}
174
+ </div>
175
+ </div>
176
+ );
177
+ }
@@ -0,0 +1,27 @@
1
+ import { useMemo } from "react";
2
+ import { stubNodeData } from "../stub-node";
3
+ import { InsideEntityPreview } from "./EntityValueChip";
4
+ import { GenericView } from "./GenericView";
5
+ import { getEntityView } from "./router";
6
+ import { useEntityNodes } from "#hooks/use-entity-nodes";
7
+
8
+ export type EntityPreviewProps = { iri: string };
9
+
10
+ // Loaded lazily from EntityValueChip: a static import would cycle through the node router back to GenericView.
11
+ export function EntityPreview(props: EntityPreviewProps) {
12
+ const { iri } = props;
13
+
14
+ const iris = useMemo(() => [iri], [iri]);
15
+ const { nodesByIri, isLoading } = useEntityNodes(iris);
16
+
17
+ if (isLoading) return <div className="typography-tiny px-3 py-2 text-muted-foreground">Loading…</div>;
18
+
19
+ const node = nodesByIri.get(iri);
20
+ const View = (node && getEntityView(node.data.type, node.data.actionShape)) ?? GenericView;
21
+
22
+ return (
23
+ <InsideEntityPreview.Provider value={true}>
24
+ <View data={node?.data ?? stubNodeData(iri)} />
25
+ </InsideEntityPreview.Provider>
26
+ );
27
+ }
@@ -0,0 +1,85 @@
1
+ import { humanizeEntityName } from "@manta-eu/client/shared/agent-mode";
2
+ import { iriLocalName } from "@manta-eu/client/shared/rdf-namespaces";
3
+ import { isPresent } from "@manta-eu/lib/present";
4
+ import { createContext, lazy, Suspense, useContext } from "react";
5
+ import { useEntityLink } from "../entity-link";
6
+ import { iriChannel } from "./card-facts";
7
+ import { ChannelIcon, channelDisplayName } from "#components/channel-icon/index";
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from "#components/tooltip/index";
9
+ import { useInstanceLabel } from "#hooks/use-instance-labels";
10
+ import { useOntologyTerm } from "#hooks/use-ontology-terms";
11
+ import { cn } from "#style";
12
+
13
+ const EntityPreview = lazy(() => import("./EntityPreview").then((module) => ({ default: module.EntityPreview })));
14
+
15
+ // A preview already shows one entity card; its own chips skip the hover so previews never stack.
16
+ export const InsideEntityPreview = createContext(false);
17
+
18
+ // An ontology term (e.g. an enum concept) has a label but no record of its own to open or preview.
19
+ export function useEntityValueLabel(iri: string): { label: string; isOntologyTerm: boolean } {
20
+ const term = useOntologyTerm(iri);
21
+ const instanceLabel = useInstanceLabel(term?.label ? undefined : iri);
22
+
23
+ return {
24
+ label: term?.label ?? instanceLabel ?? humanizeEntityName(iriLocalName(iri)),
25
+ isOntologyTerm: isPresent(term?.label),
26
+ };
27
+ }
28
+
29
+ export type EntityValueChipProps = { iri: string; color?: string };
30
+
31
+ export function EntityValueChip(props: EntityValueChipProps) {
32
+ const { iri, color } = props;
33
+
34
+ const { label, isOntologyTerm } = useEntityValueLabel(iri);
35
+ const EntityLink = useEntityLink();
36
+ const insidePreview = useContext(InsideEntityPreview);
37
+ const channel = iriChannel(iri);
38
+
39
+ if (isOntologyTerm) return <span className="min-w-0 truncate">{label}</span>;
40
+
41
+ const chipClassName = cn(
42
+ "nodrag inline-flex min-w-0 max-w-full items-center gap-1.5 rounded bg-faint px-1.5 py-px text-default-foreground",
43
+ EntityLink && "transition-colors hover:bg-accent hover:text-primary",
44
+ );
45
+ const content = (
46
+ <>
47
+ {channel ? (
48
+ <span role="img" aria-label={channelDisplayName[channel]} className="shrink-0">
49
+ <ChannelIcon channel={channel} iconClassName="size-3" />
50
+ </span>
51
+ ) : (
52
+ color && <span aria-hidden className="size-1.5 shrink-0 rounded-full" style={{ backgroundColor: color }} />
53
+ )}
54
+ <span className="min-w-0 truncate">{label}</span>
55
+ </>
56
+ );
57
+ const chip = EntityLink ? (
58
+ <EntityLink iri={iri} className={chipClassName}>
59
+ {content}
60
+ </EntityLink>
61
+ ) : (
62
+ <span className={chipClassName}>{content}</span>
63
+ );
64
+
65
+ if (insidePreview) return chip;
66
+
67
+ return (
68
+ <Tooltip>
69
+ <TooltipTrigger asChild>
70
+ <span className="inline-flex min-w-0 max-w-full">{chip}</span>
71
+ </TooltipTrigger>
72
+ <TooltipContent
73
+ side="bottom"
74
+ align="start"
75
+ collisionPadding={8}
76
+ showArrow={false}
77
+ className="rounded-lg-v4 border border-default-border bg-card p-1.5 shadow-v4"
78
+ >
79
+ <Suspense fallback={null}>
80
+ <EntityPreview iri={iri} />
81
+ </Suspense>
82
+ </TooltipContent>
83
+ </Tooltip>
84
+ );
85
+ }
@@ -1,47 +1,24 @@
1
- import { isPresent } from "@manta-eu/lib/present";
2
- import { expandPrefix } from "@manta-eu/rdf-lib/namespaces";
3
1
  import { ChevronRight } from "lucide-react";
4
2
  import { memo, useCallback, useMemo } from "react";
5
- import {
6
- getCurrencyCode,
7
- isDisplayablePreviewKey,
8
- selectPriorityPreviewFields,
9
- type PreviewFieldHints,
10
- } from "../graph-list-utils";
11
- import { ontologyTermIrisForProperties, withOntologyLabels } from "../ontology-labels";
12
- import { rdfTypeColors } from "../rdf-type-colors";
13
- import { statusColors } from "../status-utils";
14
- import { useGraphTheme } from "../themes";
15
- import type { EntityViewProps } from "../types";
3
+ import type { PreviewFieldHints } from "../graph-list-utils";
4
+ import { statusBadgeVariant } from "../status-utils";
5
+ import type { EntityViewProps, NodeProperty } from "../types";
6
+ import { AttributeList, useAttributeFacts } from "./AttributeList";
7
+ import { typeColor } from "./card-facts";
16
8
  import { getStatus } from "./node-data";
17
- import { useOntologyTerms } from "#hooks/use-ontology-terms";
9
+ import { Badge } from "#components/badge/index";
18
10
  import { cn } from "#style";
19
11
 
20
- const defaultColor = "#6b7280";
21
- const explainCardMaxFacts = 5;
22
-
23
- type CardFact = { label: string; value: string; highlighted: boolean };
24
-
25
- function getNodeColor(type?: string): string {
26
- if (!type) return defaultColor;
27
- return rdfTypeColors[type] ?? rdfTypeColors[expandPrefix(type)] ?? defaultColor;
28
- }
29
-
30
- function getStatusStyle(status: string): { bg: string; text: string } {
31
- return statusColors[status.toLowerCase()] ?? { bg: "#94a3b8", text: "#ffffff" };
32
- }
33
-
34
12
  export type GenericViewProps = EntityViewProps & {
35
13
  previewHints?: PreviewFieldHints;
36
14
  onToggleCollapse?: () => void;
15
+ defaultExpanded?: boolean;
37
16
  };
38
17
 
39
18
  export const GenericView = memo(function GenericView(props: GenericViewProps) {
40
- const { data, selected = false, onContextMenu, previewHints, onToggleCollapse } = props;
41
-
42
- const theme = useGraphTheme();
19
+ const { data, selected = false, onContextMenu, previewHints, onToggleCollapse, defaultExpanded } = props;
43
20
 
44
- const color = useMemo(() => getNodeColor(data.type), [data.type]);
21
+ const color = useMemo(() => typeColor(data.type), [data.type]);
45
22
  const hasChildren = data.hasChildren;
46
23
  const isCollapsed = data.isCollapsed;
47
24
 
@@ -55,137 +32,70 @@ export const GenericView = memo(function GenericView(props: GenericViewProps) {
55
32
 
56
33
  const status = useMemo(() => getStatus(data), [data]);
57
34
 
58
- const ontologyTerms = useOntologyTerms(
59
- useMemo(() => ontologyTermIrisForProperties(data.properties), [data.properties]),
60
- );
61
-
62
- const labeledProperties = useMemo(
63
- () => withOntologyLabels(data.properties, ontologyTerms),
64
- [data.properties, ontologyTerms],
65
- );
66
-
67
- const { displayProperties, hiddenPropertiesCount } = useMemo(() => {
68
- const properties = status ? labeledProperties.filter((prop) => prop.key !== status.key) : labeledProperties;
69
-
70
- if (previewHints) {
71
- const fields = selectPriorityPreviewFields(properties, previewHints, getCurrencyCode([], properties), {
72
- limit: explainCardMaxFacts,
73
- keepUnmatched: true,
74
- });
75
-
76
- const total = properties.filter((prop) => isDisplayablePreviewKey(prop.key)).length;
77
-
78
- const facts: CardFact[] = fields.map((field) => ({
79
- label: field.label,
80
- value: field.value,
81
- highlighted: field.isHighlighted ?? false,
82
- }));
83
-
84
- return { displayProperties: facts, hiddenPropertiesCount: Math.max(0, total - facts.length) };
85
- }
86
-
87
- const importantKeys = ["name", "title", "label", "description", "id"];
88
- const important: typeof properties = [];
89
- const other: typeof properties = [];
90
-
91
- for (const prop of properties) {
92
- const keyLower = prop.key.toLowerCase();
93
- if (importantKeys.some((k) => keyLower.includes(k))) {
94
- important.push(prop);
95
- } else {
96
- other.push(prop);
97
- }
98
- }
99
-
100
- const maxProperties = 4;
101
- const allDisplayed: CardFact[] = [...important, ...other]
102
- .slice(0, maxProperties)
103
- .map((prop) => ({ label: prop.label ?? prop.key, value: prop.value, highlighted: false }));
104
-
105
- return {
106
- displayProperties: allDisplayed,
107
- hiddenPropertiesCount: Math.max(0, important.length + other.length - maxProperties),
108
- };
109
- }, [labeledProperties, status, previewHints]);
35
+ const include = useCallback((property: NodeProperty) => !status || property.key !== status.key, [status]);
36
+ // The header badge shows the status, so an IRI-valued one must not come back as a relation row either.
37
+ const relations = useMemo(() => {
38
+ const statusIri = status ? data.properties.find((property) => property.key === status.key)?.iri : undefined;
39
+ return statusIri ? data.relations?.filter((relation) => relation.predicate !== statusIri) : data.relations;
40
+ }, [status, data.properties, data.relations]);
41
+
42
+ const { facts, previewCount } = useAttributeFacts({
43
+ properties: data.properties,
44
+ relations,
45
+ jsonLd: data.jsonLd,
46
+ title: data.label,
47
+ type: data.type,
48
+ prefixLabels: true,
49
+ previewHints,
50
+ include,
51
+ });
110
52
 
111
53
  return (
112
54
  <div
113
- className="rounded-lg shadow-md min-w-60 max-w-80 transition-all duration-200 overflow-hidden"
114
- style={{
115
- backgroundColor: theme.node.background,
116
- borderColor: color,
117
- borderWidth: 2,
118
- borderStyle: "solid",
119
- ...(selected && { boxShadow: `0 0 0 2px ${theme.node.selectedRing}` }),
120
- }}
55
+ className={cn(
56
+ "min-w-60 max-w-80 overflow-hidden rounded-md-v4 border border-muted-border bg-muted text-default-foreground transition-shadow duration-200",
57
+ selected && "ring-2 ring-active-border",
58
+ )}
121
59
  onContextMenu={onContextMenu}
122
60
  >
123
- <div className="px-3 py-2 flex items-center justify-between gap-2" style={{ backgroundColor: `${color}20` }}>
124
- <div className="flex items-center gap-2 min-w-0">
61
+ <div className="flex items-center justify-between gap-2 border-b border-muted-border px-3 py-2">
62
+ <div className="flex min-w-0 items-center gap-2">
125
63
  {hasChildren && onToggleCollapse && (
126
64
  <button
65
+ type="button"
127
66
  onClick={handleToggleClick}
128
- className={cn(
129
- "w-5 h-5 rounded flex items-center justify-center shrink-0",
130
- "bg-muted",
131
- "hover:bg-muted transition-all duration-200",
132
- )}
133
- style={{ color: theme.node.text }}
67
+ className="nodrag flex size-5 shrink-0 items-center justify-center rounded bg-accent text-muted-foreground transition-colors hover:text-default-foreground"
134
68
  title={isCollapsed ? "Expand" : "Collapse"}
135
69
  >
136
- <ChevronRight className={cn("w-3 h-3 transition-transform duration-200", !isCollapsed && "rotate-90")} />
70
+ <ChevronRight className={cn("size-3 transition-transform duration-200", !isCollapsed && "rotate-90")} />
137
71
  </button>
138
72
  )}
139
- <div className="typography-small truncate" style={{ color: theme.node.text }} title={data.label}>
73
+ <span aria-hidden className="size-2 shrink-0 rounded-full" style={{ backgroundColor: color }} />
74
+ <div className="typography-small-medium truncate" title={data.label}>
140
75
  {data.label}
141
76
  </div>
142
77
  </div>
143
- <div className="flex items-center gap-1.5 shrink-0">
78
+ <div className="flex shrink-0 items-center gap-1.5">
144
79
  {status && (
145
- <span
146
- className="px-1.5 py-0.5 rounded typography-tiny"
147
- style={{ backgroundColor: getStatusStyle(status.value).bg, color: getStatusStyle(status.value).text }}
148
- >
80
+ <Badge size="sm" variant={statusBadgeVariant(status.value)}>
149
81
  {status.value.toLowerCase()}
150
- </span>
82
+ </Badge>
151
83
  )}
152
84
  {hasChildren && isCollapsed && (
153
- <span
154
- className="px-1.5 py-0.5 rounded typography-tiny text-primary-foreground"
155
- style={{ backgroundColor: color }}
156
- >
85
+ <Badge size="sm" variant="secondary">
157
86
  +{data.childIds.length}
158
- </span>
87
+ </Badge>
159
88
  )}
160
89
  </div>
161
90
  </div>
162
91
 
163
- {isPresent(displayProperties) && (
164
- <div className="px-3 py-2 space-y-1">
165
- {displayProperties.map(({ label, value, highlighted }, index) => (
166
- <div key={index} className="flex justify-between typography-tiny gap-2">
167
- <span style={{ color: theme.node.textMuted }} className="shrink-0">
168
- {label}:
169
- </span>
170
- <span
171
- style={{
172
- color: highlighted ? theme.node.selectedRing : theme.node.text,
173
- fontWeight: highlighted ? 600 : undefined,
174
- }}
175
- className="truncate text-right tabular-nums"
176
- title={value}
177
- >
178
- {value}
179
- </span>
180
- </div>
181
- ))}
182
- {hiddenPropertiesCount > 0 && (
183
- <div className="typography-tiny italic" style={{ color: theme.node.textMuted }}>
184
- +{hiddenPropertiesCount} more
185
- </div>
186
- )}
187
- </div>
188
- )}
92
+ <AttributeList
93
+ facts={facts}
94
+ previewCount={previewCount}
95
+ defaultExpanded={defaultExpanded}
96
+ searchClassName="px-3"
97
+ bodyClassName="px-3 py-2"
98
+ />
189
99
  </div>
190
100
  );
191
101
  });