@anchrd/intel-ui 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components.json +7 -1
- package/package.json +3 -1
- package/src/app/action-slot/action-slot.tsx +23 -0
- package/src/app/app-sidebar/app-sidebar.tsx +71 -0
- package/src/app/app-tree/app-tree.tsx +798 -0
- package/src/app/app.tsx +99 -54
- package/src/app/header-search/header-search.tsx +291 -0
- package/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
- package/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
- package/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
- package/src/app/tree-move/tree-move.tsx +197 -0
- package/src/app/user-footer/user-footer.tsx +103 -0
- package/src/app/view-toggle/view-toggle.tsx +77 -0
- package/src/blocknote-view/blocknote-view.tsx +19 -2
- package/src/branding/favicon.default.svg +2 -2
- package/src/branding/favicon.svg +2 -2
- package/src/components/ui/breadcrumb.tsx +102 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/collapsible.tsx +20 -0
- package/src/components/ui/command.tsx +160 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/dropdown-menu.tsx +162 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +136 -0
- package/src/components/ui/sidebar.tsx +693 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/tooltip.tsx +51 -0
- package/src/data/intel-data-provider/intel-data-provider.ts +170 -85
- package/src/data/intel-data-provider/intel-data-provider.types.ts +64 -20
- package/src/document-link/document-link.tsx +132 -0
- package/src/flow-runs/flow-runs.tsx +225 -0
- package/src/flows/flows.tsx +684 -368
- package/src/flows/node-icon/node-icon.ts +28 -0
- package/src/flows/node-palette/node-palette.tsx +174 -0
- package/src/flows/node-palette/node-palette.types.ts +15 -0
- package/src/graph-pane/graph-pane.tsx +44 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/i18n/en.json +188 -52
- package/src/knowledge/knowledge.tsx +133 -709
- package/src/knowledge-editor/knowledge-editor.tsx +169 -21
- package/src/knowledge-graph/knowledge-graph.ts +26 -24
- package/src/knowledge-graph/knowledge-graph.tsx +33 -24
- package/src/knowledge-table/knowledge-table.tsx +129 -0
- package/src/main.tsx +2 -2
- package/src/resource-menu/resource-menu.tsx +580 -0
- package/src/router/router.tsx +4 -0
- package/src/router/selection-search.ts +43 -0
- package/src/save-button/save-button.tsx +103 -0
- package/src/styles.css +91 -51
- package/src/theme/theme.ts +24 -0
- package/src/tools/tools.tsx +175 -158
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useBlocker } from "@tanstack/react-router";
|
|
2
|
+
import { Save } from "lucide-react";
|
|
3
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
4
|
+
import { Modal } from "@/modal/modal.tsx";
|
|
5
|
+
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Saving, in the title line beside the name of the thing being saved (#24).
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ Unsaved is a state that must survive colour blindness, so it is never carried by colour alone.
|
|
11
|
+
* At rest the button is a quiet icon; with something to save it fills, grows a dot and spells the
|
|
12
|
+
* word out. Three changes at once, of which two are readable without seeing any colour at all.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ The accessible name names the state as well — "Saved" and "Save unsaved changes" are two
|
|
15
|
+
* different pieces of information, and a screen reader gets neither the fill nor the dot. The
|
|
16
|
+
* visible word stays a prefix of it, so speech input still reaches the button by what it reads.
|
|
17
|
+
*/
|
|
18
|
+
export function SaveButton({
|
|
19
|
+
dirty,
|
|
20
|
+
saving,
|
|
21
|
+
onSave,
|
|
22
|
+
}: {
|
|
23
|
+
dirty: boolean;
|
|
24
|
+
saving: boolean;
|
|
25
|
+
onSave(): void;
|
|
26
|
+
}) {
|
|
27
|
+
const { i18n } = useIntelRouterContext();
|
|
28
|
+
const label = i18n.t(dirty ? "common.saveDirty" : "common.saved");
|
|
29
|
+
return (
|
|
30
|
+
<TooltipProvider delayDuration={300}>
|
|
31
|
+
<Tooltip>
|
|
32
|
+
{/* ⚠️ The wrapper is the trigger, not the button. At rest the button is disabled, and a
|
|
33
|
+
disabled control fires no pointer events — a tooltip hung on it would stay shut in the
|
|
34
|
+
one state where the button carries no words at all. */}
|
|
35
|
+
<TooltipTrigger asChild>
|
|
36
|
+
<span className="inline-flex">
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
disabled={!dirty || saving}
|
|
40
|
+
onClick={onSave}
|
|
41
|
+
aria-label={label}
|
|
42
|
+
data-dirty={dirty}
|
|
43
|
+
className={
|
|
44
|
+
dirty
|
|
45
|
+
? "inline-flex h-8 items-center gap-2 rounded-md bg-primary px-2.5 text-sm font-medium text-primary-foreground outline-none hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-70"
|
|
46
|
+
: "inline-flex size-8 items-center justify-center rounded-md border bg-background text-muted-foreground outline-none disabled:opacity-60"
|
|
47
|
+
}
|
|
48
|
+
>
|
|
49
|
+
{dirty ? (
|
|
50
|
+
<span aria-hidden="true" className="size-1.5 shrink-0 rounded-full bg-current" />
|
|
51
|
+
) : null}
|
|
52
|
+
<Save aria-hidden="true" className="size-4 shrink-0" />
|
|
53
|
+
{dirty ? (
|
|
54
|
+
<span>{saving ? i18n.t("common.saving") : i18n.t("common.save")}</span>
|
|
55
|
+
) : null}
|
|
56
|
+
</button>
|
|
57
|
+
</span>
|
|
58
|
+
</TooltipTrigger>
|
|
59
|
+
<TooltipContent>{label}</TooltipContent>
|
|
60
|
+
</Tooltip>
|
|
61
|
+
</TooltipProvider>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The state, said out loud at the moment it would otherwise be lost.
|
|
67
|
+
*
|
|
68
|
+
* ⚠️ Without this the button is the only carrier of "not saved yet", and the one person who needs
|
|
69
|
+
* that answer is the one who has already looked away — clicking the next document in the tree is a
|
|
70
|
+
* plain navigation, and the editor would unmount with the work in it. `enableBeforeUnload` covers
|
|
71
|
+
* the other exit, the one the router never sees: closing the tab or reloading.
|
|
72
|
+
*/
|
|
73
|
+
export function UnsavedChangesGuard({ dirty }: { dirty: boolean }) {
|
|
74
|
+
const { i18n } = useIntelRouterContext();
|
|
75
|
+
const blocker = useBlocker({
|
|
76
|
+
shouldBlockFn: () => true,
|
|
77
|
+
disabled: !dirty,
|
|
78
|
+
enableBeforeUnload: () => dirty,
|
|
79
|
+
withResolver: true,
|
|
80
|
+
});
|
|
81
|
+
if (blocker.status !== "blocked") return null;
|
|
82
|
+
return (
|
|
83
|
+
<Modal title={i18n.t("common.unsavedTitle")} close={blocker.reset}>
|
|
84
|
+
<p className="text-sm text-muted-foreground">{i18n.t("common.unsavedBody")}</p>
|
|
85
|
+
<div className="mt-5 flex gap-2">
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
onClick={blocker.reset}
|
|
89
|
+
className="flex-1 rounded-md border px-4 py-2 text-sm font-medium outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
|
90
|
+
>
|
|
91
|
+
{i18n.t("common.unsavedStay")}
|
|
92
|
+
</button>
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
onClick={blocker.proceed}
|
|
96
|
+
className="flex-1 rounded-md bg-destructive px-4 py-2 text-sm font-medium text-destructive-foreground outline-none hover:bg-destructive/90 focus-visible:ring-2 focus-visible:ring-ring"
|
|
97
|
+
>
|
|
98
|
+
{i18n.t("common.unsavedLeave")}
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
</Modal>
|
|
102
|
+
);
|
|
103
|
+
}
|
package/src/styles.css
CHANGED
|
@@ -7,65 +7,68 @@
|
|
|
7
7
|
|
|
8
8
|
@custom-variant dark (&:is(.dark *));
|
|
9
9
|
|
|
10
|
+
/* The default theme is gate's, token for token: an achromatic base whose primary is black rather
|
|
11
|
+
than Intel's former blue (hue 265). Every value here is a default — `theme/custom.css` is loaded
|
|
12
|
+
after this file and a customer theme overrides any of them without touching a component. */
|
|
10
13
|
:root {
|
|
11
14
|
--radius: 0.625rem;
|
|
12
|
-
--background: oklch(0
|
|
13
|
-
--foreground: oklch(0.
|
|
15
|
+
--background: oklch(1 0 0);
|
|
16
|
+
--foreground: oklch(0.145 0 0);
|
|
14
17
|
--card: oklch(1 0 0);
|
|
15
|
-
--card-foreground: oklch(0.
|
|
18
|
+
--card-foreground: oklch(0.145 0 0);
|
|
16
19
|
--popover: oklch(1 0 0);
|
|
17
|
-
--popover-foreground: oklch(0.
|
|
18
|
-
--primary: oklch(0.
|
|
19
|
-
--primary-foreground: oklch(0.
|
|
20
|
-
--secondary: oklch(0.
|
|
21
|
-
--secondary-foreground: oklch(0.
|
|
22
|
-
--muted: oklch(0.
|
|
23
|
-
--muted-foreground: oklch(0.
|
|
24
|
-
--accent: oklch(0.
|
|
25
|
-
--accent-foreground: oklch(0.
|
|
26
|
-
--destructive: oklch(0.
|
|
27
|
-
--destructive-foreground: oklch(0.
|
|
28
|
-
--border: oklch(0.
|
|
29
|
-
--input: oklch(0.
|
|
30
|
-
--ring: oklch(0.
|
|
31
|
-
--sidebar: oklch(0.
|
|
32
|
-
--sidebar-foreground: oklch(0.
|
|
33
|
-
--sidebar-primary: oklch(0.
|
|
34
|
-
--sidebar-primary-foreground: oklch(0.
|
|
35
|
-
--sidebar-accent: oklch(0.
|
|
36
|
-
--sidebar-accent-foreground: oklch(0.
|
|
37
|
-
--sidebar-border: oklch(0.
|
|
38
|
-
--sidebar-ring: oklch(0.
|
|
20
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
21
|
+
--primary: oklch(0.205 0 0);
|
|
22
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
23
|
+
--secondary: oklch(0.97 0 0);
|
|
24
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
25
|
+
--muted: oklch(0.97 0 0);
|
|
26
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
27
|
+
--accent: oklch(0.97 0 0);
|
|
28
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
29
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
30
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
31
|
+
--border: oklch(0.922 0 0);
|
|
32
|
+
--input: oklch(0.922 0 0);
|
|
33
|
+
--ring: oklch(0.708 0 0);
|
|
34
|
+
--sidebar: oklch(0.985 0 0);
|
|
35
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
36
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
37
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
38
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
39
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
40
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
41
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
.dark {
|
|
42
|
-
--background: oklch(0.
|
|
43
|
-
--foreground: oklch(0.
|
|
44
|
-
--card: oklch(0.
|
|
45
|
-
--card-foreground: oklch(0.
|
|
46
|
-
--popover: oklch(0.
|
|
47
|
-
--popover-foreground: oklch(0.
|
|
48
|
-
--primary: oklch(0.
|
|
49
|
-
--primary-foreground: oklch(0.
|
|
50
|
-
--secondary: oklch(0.
|
|
51
|
-
--secondary-foreground: oklch(0.
|
|
52
|
-
--muted: oklch(0.
|
|
53
|
-
--muted-foreground: oklch(0.
|
|
54
|
-
--accent: oklch(0.
|
|
55
|
-
--accent-foreground: oklch(0.
|
|
56
|
-
--destructive: oklch(0.
|
|
57
|
-
--destructive-foreground: oklch(0.
|
|
58
|
-
--border: oklch(1 0 0 /
|
|
59
|
-
--input: oklch(1 0 0 /
|
|
60
|
-
--ring: oklch(0.
|
|
61
|
-
--sidebar: oklch(0.
|
|
62
|
-
--sidebar-foreground: oklch(0.
|
|
63
|
-
--sidebar-primary: oklch(0.
|
|
64
|
-
--sidebar-primary-foreground: oklch(0.
|
|
65
|
-
--sidebar-accent: oklch(0.
|
|
66
|
-
--sidebar-accent-foreground: oklch(0.
|
|
45
|
+
--background: oklch(0.145 0 0);
|
|
46
|
+
--foreground: oklch(0.985 0 0);
|
|
47
|
+
--card: oklch(0.205 0 0);
|
|
48
|
+
--card-foreground: oklch(0.985 0 0);
|
|
49
|
+
--popover: oklch(0.205 0 0);
|
|
50
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
51
|
+
--primary: oklch(0.922 0 0);
|
|
52
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
53
|
+
--secondary: oklch(0.269 0 0);
|
|
54
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
55
|
+
--muted: oklch(0.269 0 0);
|
|
56
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
57
|
+
--accent: oklch(0.269 0 0);
|
|
58
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
59
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
60
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
61
|
+
--border: oklch(1 0 0 / 10%);
|
|
62
|
+
--input: oklch(1 0 0 / 15%);
|
|
63
|
+
--ring: oklch(0.556 0 0);
|
|
64
|
+
--sidebar: oklch(0.205 0 0);
|
|
65
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
66
|
+
--sidebar-primary: oklch(0.922 0 0);
|
|
67
|
+
--sidebar-primary-foreground: oklch(0.205 0 0);
|
|
68
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
69
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
67
70
|
--sidebar-border: oklch(1 0 0 / 10%);
|
|
68
|
-
--sidebar-ring: oklch(0.
|
|
71
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
@theme inline {
|
|
@@ -111,6 +114,43 @@
|
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
/* `colorMode` on `<ReactFlow>` decides which of the library's two palettes it paints from; these
|
|
118
|
+
decide what the palette is made of. They are React Flow's own theming variables — its documented
|
|
119
|
+
extension point, set on its root class, not overrides hung on the internal class names of its
|
|
120
|
+
controls — so an installation with its own theme colours the canvas, the grid, the zoom buttons
|
|
121
|
+
and the overview map along with everything else. Same arrangement as `.react-sigma` below. */
|
|
122
|
+
.react-flow {
|
|
123
|
+
--xy-background-color: var(--background);
|
|
124
|
+
--xy-background-pattern-color: var(--border);
|
|
125
|
+
--xy-controls-button-background-color: var(--card);
|
|
126
|
+
--xy-controls-button-background-color-hover: var(--accent);
|
|
127
|
+
--xy-controls-button-color: var(--card-foreground);
|
|
128
|
+
--xy-controls-button-color-hover: var(--accent-foreground);
|
|
129
|
+
--xy-controls-button-border-color: var(--border);
|
|
130
|
+
--xy-minimap-background-color: var(--card);
|
|
131
|
+
/* The nodes have to read as nodes against the map's own background rather than as one white
|
|
132
|
+
block, so they take the token that is defined to contrast with it. */
|
|
133
|
+
--xy-minimap-node-background-color: var(--muted-foreground);
|
|
134
|
+
/* The veil over everything outside the viewport: a token, thinned, because opaque would hide the
|
|
135
|
+
part of the graph the map exists to show. */
|
|
136
|
+
--xy-minimap-mask-background-color: color-mix(in oklab, var(--muted) 70%, transparent);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* A context edge is not a step in the order of work (#37), so it is drawn as material rather than as
|
|
140
|
+
procedure: dashed, thinner, and in the muted token instead of the edge colour. The dash pattern is
|
|
141
|
+
set on the edge itself; the colour belongs here, with the rest of the canvas theming, so an
|
|
142
|
+
installation's own tokens reach it too. */
|
|
143
|
+
.react-flow .intel-context-edge .react-flow__edge-path {
|
|
144
|
+
stroke: var(--muted-foreground);
|
|
145
|
+
stroke-width: 1.5;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* The start node in the overview map (#39). Nothing is written there, so the only thing that can
|
|
149
|
+
carry "this is where it begins" that far out is the colour. */
|
|
150
|
+
.react-flow__minimap-node.intel-minimap-start {
|
|
151
|
+
fill: var(--primary);
|
|
152
|
+
}
|
|
153
|
+
|
|
114
154
|
.react-sigma {
|
|
115
155
|
--sigma-background-color: var(--background);
|
|
116
156
|
--sigma-controls-background-color: var(--card);
|
package/src/theme/theme.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
|
|
1
3
|
export type Theme = "dark" | "light";
|
|
2
4
|
|
|
5
|
+
// The one query the whole theme hangs off. Exported so that the `.dark` class and everything that
|
|
6
|
+
// has to agree with it read the same string rather than two copies of it.
|
|
7
|
+
export const SystemThemeQuery = "(prefers-color-scheme: dark)";
|
|
8
|
+
|
|
3
9
|
export function resolveTheme(prefersDark: boolean): Theme {
|
|
4
10
|
return prefersDark ? "dark" : "light";
|
|
5
11
|
}
|
|
@@ -14,3 +20,21 @@ export function watchSystemTheme(root: HTMLElement, media: MediaQueryList): () =
|
|
|
14
20
|
media.addEventListener("change", onChange);
|
|
15
21
|
return () => media.removeEventListener("change", onChange);
|
|
16
22
|
}
|
|
23
|
+
|
|
24
|
+
function subscribeSystemTheme(onChange: () => void): () => void {
|
|
25
|
+
const media = window.matchMedia(SystemThemeQuery);
|
|
26
|
+
media.addEventListener("change", onChange);
|
|
27
|
+
return () => media.removeEventListener("change", onChange);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function systemTheme(): Theme {
|
|
31
|
+
return resolveTheme(window.matchMedia(SystemThemeQuery).matches);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The theme as a value, for the one consumer a CSS class cannot serve: a foreign library that
|
|
35
|
+
// paints from a prop instead of from our tokens. It reads the same media query `watchSystemTheme`
|
|
36
|
+
// puts the `.dark` class on the root from, so the two cannot drift apart — and because it is a
|
|
37
|
+
// subscription rather than a read, a switch mid-session moves both at once, without a reload.
|
|
38
|
+
export function useSystemTheme(): Theme {
|
|
39
|
+
return useSyncExternalStore(subscribeSystemTheme, systemTheme);
|
|
40
|
+
}
|