@dxos/react-ui-rdf 0.0.0 → 0.11.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 (31) hide show
  1. package/dist/lib/index.mjs +338 -0
  2. package/dist/lib/index.mjs.map +1 -0
  3. package/dist/types/src/components/FactViewer/FactViewer.d.ts +73 -0
  4. package/dist/types/src/components/FactViewer/FactViewer.d.ts.map +1 -0
  5. package/dist/types/src/{FactViewer.stories.d.ts → components/FactViewer/FactViewer.stories.d.ts} +4 -2
  6. package/dist/types/src/components/FactViewer/FactViewer.stories.d.ts.map +1 -0
  7. package/dist/types/src/components/FactViewer/FactViewer.theme.d.ts +74 -0
  8. package/dist/types/src/components/FactViewer/FactViewer.theme.d.ts.map +1 -0
  9. package/dist/types/src/components/FactViewer/index.d.ts +2 -0
  10. package/dist/types/src/components/FactViewer/index.d.ts.map +1 -0
  11. package/dist/types/src/components/index.d.ts +2 -0
  12. package/dist/types/src/components/index.d.ts.map +1 -0
  13. package/dist/types/src/index.d.ts +1 -1
  14. package/dist/types/tsconfig.tsbuildinfo +1 -1
  15. package/package.json +12 -13
  16. package/src/{FactViewer.stories.tsx → components/FactViewer/FactViewer.stories.tsx} +15 -2
  17. package/src/components/FactViewer/FactViewer.theme.ts +51 -0
  18. package/src/components/FactViewer/FactViewer.tsx +289 -0
  19. package/src/components/FactViewer/index.ts +5 -0
  20. package/src/components/index.ts +5 -0
  21. package/src/index.ts +1 -1
  22. package/dist/lib/browser/index.mjs +0 -249
  23. package/dist/lib/browser/index.mjs.map +0 -7
  24. package/dist/lib/browser/meta.json +0 -1
  25. package/dist/lib/node-esm/index.mjs +0 -251
  26. package/dist/lib/node-esm/index.mjs.map +0 -7
  27. package/dist/lib/node-esm/meta.json +0 -1
  28. package/dist/types/src/FactViewer.d.ts +0 -18
  29. package/dist/types/src/FactViewer.d.ts.map +0 -1
  30. package/dist/types/src/FactViewer.stories.d.ts.map +0 -1
  31. package/src/FactViewer.tsx +0 -161
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-rdf",
3
- "version": "0.0.0",
3
+ "version": "0.11.1",
4
4
  "description": "Presentational viewer for extracted semantic facts.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -16,8 +16,7 @@
16
16
  ".": {
17
17
  "source": "./src/index.ts",
18
18
  "types": "./dist/types/src/index.d.ts",
19
- "browser": "./dist/lib/browser/index.mjs",
20
- "node": "./dist/lib/node-esm/index.mjs"
19
+ "import": "./dist/lib/index.mjs"
21
20
  }
22
21
  },
23
22
  "types": "dist/types/src/index.d.ts",
@@ -26,10 +25,10 @@
26
25
  "src"
27
26
  ],
28
27
  "dependencies": {
29
- "@dxos/pipeline-rdf": "0.0.0",
30
- "@dxos/ui-types": "0.10.0",
31
- "@dxos/react-ui-graph": "0.10.0",
32
- "@dxos/react-ui-list": "0.10.0"
28
+ "@dxos/pipeline-rdf": "0.11.1",
29
+ "@dxos/react-ui-graph": "0.11.1",
30
+ "@dxos/ui-types": "0.11.1",
31
+ "@dxos/react-ui-list": "0.11.1"
33
32
  },
34
33
  "devDependencies": {
35
34
  "@storybook/react-vite": "^10.4.2",
@@ -37,16 +36,16 @@
37
36
  "@types/react-dom": "~19.2.3",
38
37
  "react": "~19.2.7",
39
38
  "react-dom": "~19.2.7",
40
- "vite": "^8.0.16",
41
- "@dxos/react-ui": "0.10.0",
42
- "@dxos/ui-theme": "0.10.0",
43
- "@dxos/storybook-utils": "0.10.0"
39
+ "vite": "^8.1.4",
40
+ "@dxos/react-ui": "0.11.1",
41
+ "@dxos/storybook-utils": "0.11.1",
42
+ "@dxos/ui-theme": "0.11.1"
44
43
  },
45
44
  "peerDependencies": {
46
45
  "react": "~19.2.7",
47
46
  "react-dom": "~19.2.7",
48
- "@dxos/react-ui": "0.10.0",
49
- "@dxos/ui-theme": "0.10.0"
47
+ "@dxos/react-ui": "0.11.1",
48
+ "@dxos/ui-theme": "0.11.1"
50
49
  },
51
50
  "publishConfig": {
52
51
  "access": "public"
@@ -17,7 +17,7 @@ import React from 'react';
17
17
  import { type RDF } from '@dxos/pipeline-rdf';
18
18
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
19
19
 
20
- import { FactViewer, type FactViewerProps } from './FactViewer';
20
+ import { FactViewer, type FactViewerRootProps } from './FactViewer';
21
21
 
22
22
  const extractor = {
23
23
  id: 'default',
@@ -88,7 +88,7 @@ const DEMO_FACTS: RDF.Fact[] = [
88
88
  },
89
89
  ] satisfies RDF.Fact[];
90
90
 
91
- const DefaultStory = (props: FactViewerProps) => <FactViewer {...props} />;
91
+ const DefaultStory = (props: FactViewerRootProps) => <FactViewer.Root {...props} />;
92
92
 
93
93
  const meta = {
94
94
  title: 'ui/react-ui-rdf/FactViewer',
@@ -111,3 +111,16 @@ export const Empty: Story = {
111
111
  facts: [],
112
112
  },
113
113
  };
114
+
115
+ /** Explicit composition of the parts (`FactViewer.Toolbar` + `FactViewer.List`) under `FactViewer.Root`. */
116
+ export const Composed: Story = {
117
+ args: {
118
+ facts: DEMO_FACTS,
119
+ },
120
+ render: ({ facts }) => (
121
+ <FactViewer.Root facts={facts}>
122
+ <FactViewer.Toolbar />
123
+ <FactViewer.List />
124
+ </FactViewer.Root>
125
+ ),
126
+ };
@@ -0,0 +1,51 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { tv } from '@dxos/ui-theme';
6
+
7
+ /**
8
+ * Tailwind-variants theme for the {@link FactViewer} composite. Consumers call
9
+ * `factViewerTheme.styles()` for the per-slot class functions; each accepts an optional
10
+ * `{ class: … }` override for per-instance merging (mirrors the react-ui-list theme pattern).
11
+ */
12
+ const factViewerStyles = tv({
13
+ slots: {
14
+ // Toolbar spacer pushing the view toggles to the inline-end edge.
15
+ toolbarSpacer: 'grow',
16
+
17
+ // List view.
18
+ listViewport: 'flex flex-col gap-2 py-1',
19
+
20
+ // Graph view.
21
+ graphContent: 'overflow-hidden',
22
+ graphTree: 'w-full h-full',
23
+
24
+ // Subject group card.
25
+ group: 'shrink-0 flex flex-col dx-card-surface border border-subdued-separator rounded-sm overflow-hidden',
26
+ groupHeader: 'flex px-3 py-1 items-center justify-between',
27
+ groupConflict: 'flex items-center gap-1',
28
+
29
+ // Fact row.
30
+ row: 'flex flex-col items-stretch gap-2 px-3 py-1',
31
+ rowQuote: 'text-sm text-description italic',
32
+ rowGrid: 'grid grid-cols-[1fr_5rem] items-center justify-between gap-2',
33
+ rowTriple: 'w-full grid grid-cols-[1fr_1rem_1fr_1rem_1fr] items-center flex-wrap',
34
+ cell: 'bg-input-surface border border-subdued-separator rounded-sm px-2 py-0.5 font-medium whitespace-nowrap truncate',
35
+ cellDivider: 'border border-subdued-separator',
36
+ rowMeta: 'flex items-center justify-end gap-2 shrink-0',
37
+ rowConfidence: 'text-xs text-subdued',
38
+ rowAttribution: 'text-xs text-subdued text-right',
39
+ },
40
+ variants: {
41
+ // Conflicting facts get a warning rule on the inline-start edge.
42
+ conflicting: {
43
+ true: { row: 'border-is-2 border-warning-border' },
44
+ },
45
+ },
46
+ });
47
+
48
+ /** react-ui-rdf FactViewer theme: call `.styles()` for per-slot class functions. */
49
+ export const factViewerTheme = {
50
+ styles: factViewerStyles,
51
+ };
@@ -0,0 +1,289 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React, { type ReactNode, createContext, forwardRef, useContext, useMemo, useState } from 'react';
6
+
7
+ import { type RDF, buildFactGraph, factSourceFromFacts } from '@dxos/pipeline-rdf';
8
+ import { Icon, IconButton, Input, Panel, ScrollArea, Tag, type ThemedClassName, Toolbar } from '@dxos/react-ui';
9
+ import { Tree } from '@dxos/react-ui-graph';
10
+ import { Empty, Listbox } from '@dxos/react-ui-list';
11
+ import { mx } from '@dxos/ui-theme';
12
+
13
+ import { type Group, factualityColor, formatDate, formatTerm, graphToTreeNode, groupFacts, termKey } from '../../types';
14
+ import { factViewerTheme } from './FactViewer.theme';
15
+
16
+ const styles = factViewerTheme.styles();
17
+
18
+ type View = 'list' | 'graph';
19
+
20
+ //
21
+ // Context
22
+ //
23
+
24
+ type FactViewerContextValue = {
25
+ facts: RDF.Fact[];
26
+ context?: string;
27
+ predicate?: string;
28
+ groups: Group[];
29
+ graph: ReturnType<typeof graphToTreeNode> | undefined;
30
+ filter: string;
31
+ setFilter: (value: string) => void;
32
+ view: View;
33
+ setView: (view: View) => void;
34
+ };
35
+
36
+ const FactViewerContext = createContext<FactViewerContextValue | null>(null);
37
+
38
+ const useFactViewerContext = (consumer: string): FactViewerContextValue => {
39
+ const context = useContext(FactViewerContext);
40
+ if (!context) {
41
+ throw new Error(`FactViewer.${consumer} must be rendered within FactViewer.Root`);
42
+ }
43
+ return context;
44
+ };
45
+
46
+ //
47
+ // Root
48
+ //
49
+
50
+ type FactViewerRootProps = ThemedClassName<{
51
+ facts: RDF.Fact[];
52
+ /** Context entity id; scopes the list and roots the graph. */
53
+ context?: string;
54
+ /** Predicate filter; when set, only facts with this predicate are shown. */
55
+ predicate?: string;
56
+ /** Custom composition; when omitted the default toolbar + list/graph body is rendered. */
57
+ children?: ReactNode;
58
+ }>;
59
+
60
+ /**
61
+ * Viewer for extracted semantic facts with two views: a grouped **list** (by subject entity, with
62
+ * conflicts highlighted) and a **graph** (tidy tree rooted at the context entity). A `context` entity
63
+ * scopes the list and roots the graph; a `predicate` further filters the list. Pure/presentational.
64
+ *
65
+ * Renders a default toolbar + body when given no children; pass children to compose the parts
66
+ * (`FactViewer.Toolbar`, `FactViewer.List`, `FactViewer.Graph`) directly.
67
+ */
68
+ const FactViewerRoot = forwardRef<HTMLDivElement, FactViewerRootProps>(
69
+ ({ classNames, facts, context, predicate, children }, forwardedRef) => {
70
+ const [filter, setFilter] = useState('');
71
+ const [view, setView] = useState<View>('list');
72
+ const scoped = useMemo(
73
+ () =>
74
+ facts.filter(
75
+ (fact) =>
76
+ (context == null ||
77
+ termKey(fact.assertion.subject) === context ||
78
+ termKey(fact.assertion.object) === context) &&
79
+ (predicate == null || fact.assertion.predicate === predicate),
80
+ ),
81
+ [facts, context, predicate],
82
+ );
83
+ const groups = useMemo(() => groupFacts(scoped, filter), [scoped, filter]);
84
+ const graph = useMemo(
85
+ () =>
86
+ context == null ? undefined : graphToTreeNode(buildFactGraph(context, factSourceFromFacts(facts)), context),
87
+ [facts, context],
88
+ );
89
+
90
+ const value = useMemo<FactViewerContextValue>(
91
+ () => ({ facts, context, predicate, groups, graph, filter, setFilter, view, setView }),
92
+ [facts, context, predicate, groups, graph, filter, view],
93
+ );
94
+
95
+ return (
96
+ <FactViewerContext.Provider value={value}>
97
+ <Panel.Root classNames={classNames} ref={forwardedRef}>
98
+ {children ?? (
99
+ <>
100
+ <FactViewerToolbar />
101
+ {view === 'list' ? <FactViewerList /> : <FactViewerGraph />}
102
+ </>
103
+ )}
104
+ </Panel.Root>
105
+ </FactViewerContext.Provider>
106
+ );
107
+ },
108
+ );
109
+
110
+ FactViewerRoot.displayName = 'FactViewer.Root';
111
+
112
+ //
113
+ // Toolbar
114
+ //
115
+
116
+ type FactViewerToolbarProps = ThemedClassName<{}>;
117
+
118
+ const FactViewerToolbar = ({ classNames }: FactViewerToolbarProps) => {
119
+ const { filter, setFilter, view, setView } = useFactViewerContext('Toolbar');
120
+ return (
121
+ <Panel.Toolbar asChild>
122
+ <Toolbar.Root classNames={classNames}>
123
+ <Input.Root>
124
+ <Input.Label srOnly>Filter facts</Input.Label>
125
+ <Input.TextInput
126
+ placeholder='Filter by entity or predicate…'
127
+ value={filter}
128
+ onChange={(event) => setFilter(event.target.value)}
129
+ />
130
+ </Input.Root>
131
+ <div className={styles.toolbarSpacer()} />
132
+ <IconButton
133
+ icon='ph--list--regular'
134
+ iconOnly
135
+ label='List view'
136
+ variant={view === 'list' ? 'primary' : 'default'}
137
+ onClick={() => setView('list')}
138
+ />
139
+ <IconButton
140
+ icon='ph--graph--regular'
141
+ iconOnly
142
+ label='Graph view'
143
+ variant={view === 'graph' ? 'primary' : 'default'}
144
+ onClick={() => setView('graph')}
145
+ />
146
+ </Toolbar.Root>
147
+ </Panel.Toolbar>
148
+ );
149
+ };
150
+
151
+ FactViewerToolbar.displayName = 'FactViewer.Toolbar';
152
+
153
+ //
154
+ // List
155
+ //
156
+
157
+ type FactViewerListProps = ThemedClassName<{}>;
158
+
159
+ const FactViewerList = ({ classNames }: FactViewerListProps) => {
160
+ const { groups } = useFactViewerContext('List');
161
+ return (
162
+ <Panel.Content asChild>
163
+ <ScrollArea.Root padding classNames={classNames}>
164
+ <ScrollArea.Viewport classNames={styles.listViewport()}>
165
+ {groups.length === 0 && <Empty label='No facts.' />}
166
+ {groups.map((group) => (
167
+ <FactViewerGroup key={group.subject} group={group} />
168
+ ))}
169
+ </ScrollArea.Viewport>
170
+ </ScrollArea.Root>
171
+ </Panel.Content>
172
+ );
173
+ };
174
+
175
+ FactViewerList.displayName = 'FactViewer.List';
176
+
177
+ //
178
+ // Graph
179
+ //
180
+
181
+ type FactViewerGraphProps = ThemedClassName<{}>;
182
+
183
+ const FactViewerGraph = ({ classNames }: FactViewerGraphProps) => {
184
+ const { graph } = useFactViewerContext('Graph');
185
+ return (
186
+ <Panel.Content classNames={mx(styles.graphContent(), classNames)}>
187
+ {graph ? (
188
+ <Tree data={graph} variant='tidy' margin={80} classNames={styles.graphTree()} />
189
+ ) : (
190
+ <Empty icon='ph--graph--regular' label='Select an entity to root the graph.' />
191
+ )}
192
+ </Panel.Content>
193
+ );
194
+ };
195
+
196
+ FactViewerGraph.displayName = 'FactViewer.Graph';
197
+
198
+ //
199
+ // Group
200
+ //
201
+
202
+ type FactViewerGroupProps = ThemedClassName<{ group: Group }>;
203
+
204
+ const FactViewerGroup = forwardRef<HTMLDivElement, FactViewerGroupProps>(({ classNames, group }, forwardedRef) => (
205
+ <div className={styles.group({ class: mx(classNames) })} ref={forwardedRef}>
206
+ <div className={styles.groupHeader()}>
207
+ <h3>{group.subject}</h3>
208
+ {group.conflicted && (
209
+ <Tag hue='warning'>
210
+ <span className={styles.groupConflict()}>
211
+ <Icon icon='ph--warning--regular' size={3} />
212
+ conflict
213
+ </span>
214
+ </Tag>
215
+ )}
216
+ </div>
217
+ <Listbox.Root>
218
+ <Listbox.Content aria-label={group.subject}>
219
+ {group.facts.map((fact) => (
220
+ <FactViewerRow key={fact.id} fact={fact} conflicting={group.conflictedIds.has(fact.id)} />
221
+ ))}
222
+ </Listbox.Content>
223
+ </Listbox.Root>
224
+ </div>
225
+ ));
226
+
227
+ FactViewerGroup.displayName = 'FactViewer.Group';
228
+
229
+ //
230
+ // Row
231
+ //
232
+
233
+ type FactViewerRowProps = ThemedClassName<{ fact: RDF.Fact; conflicting?: boolean }>;
234
+
235
+ const FactViewerRow = forwardRef<HTMLLIElement, FactViewerRowProps>(
236
+ ({ classNames, fact, conflicting }, forwardedRef) => {
237
+ const { assertion, factuality, attribution } = fact;
238
+ return (
239
+ <Listbox.Item id={fact.id} classNames={styles.row({ conflicting, class: mx(classNames) })} ref={forwardedRef}>
240
+ {assertion.quote && <div className={styles.rowQuote()}>"{assertion.quote}"</div>}
241
+
242
+ <div className={styles.rowGrid()}>
243
+ <div className={styles.rowTriple()}>
244
+ <span className={styles.cell({ class: 'text-right' })}>{formatTerm(assertion.subject)}</span>
245
+ <span className={styles.cellDivider()} />
246
+ <span className={styles.cell({ class: 'text-center text-description' })}>{assertion.predicate}</span>
247
+ <span className={styles.cellDivider()} />
248
+ <span className={styles.cell()}>{formatTerm(assertion.object)}</span>
249
+ </div>
250
+
251
+ <div className={styles.rowMeta()}>
252
+ {factuality.confidence != null && (
253
+ <span className={styles.rowConfidence()}>{Math.round(factuality.confidence * 100)}%</span>
254
+ )}
255
+ <Tag hue={factualityColor(factuality.value)}>{factuality.value}</Tag>
256
+ </div>
257
+ </div>
258
+
259
+ <div className={styles.rowAttribution()}>
260
+ {[attribution.agent, attribution.source, formatDate(attribution.generatedAtTime)].filter(Boolean).join(' · ')}
261
+ </div>
262
+ </Listbox.Item>
263
+ );
264
+ },
265
+ );
266
+
267
+ FactViewerRow.displayName = 'FactViewer.Row';
268
+
269
+ //
270
+ // FactViewer
271
+ //
272
+
273
+ export const FactViewer = {
274
+ Root: FactViewerRoot,
275
+ Toolbar: FactViewerToolbar,
276
+ List: FactViewerList,
277
+ Graph: FactViewerGraph,
278
+ Group: FactViewerGroup,
279
+ Row: FactViewerRow,
280
+ };
281
+
282
+ export type {
283
+ FactViewerGraphProps,
284
+ FactViewerGroupProps,
285
+ FactViewerListProps,
286
+ FactViewerRootProps,
287
+ FactViewerRowProps,
288
+ FactViewerToolbarProps,
289
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './FactViewer';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './FactViewer';
package/src/index.ts CHANGED
@@ -2,5 +2,5 @@
2
2
  // Copyright 2026 DXOS.org
3
3
  //
4
4
 
5
- export * from './FactViewer';
5
+ export * from './components';
6
6
  export * from './types';