@anchrd/intel-ui 0.39.0 → 0.41.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 (36) hide show
  1. package/package.json +13 -2
  2. package/src/access-summary/access-summary.tsx +1 -11
  3. package/src/app/app-tree/app-tree.tsx +58 -0
  4. package/src/attachment-viewer/attachment-viewer.tsx +304 -0
  5. package/src/board/board-assignee/board-assignee.ts +18 -0
  6. package/src/board/board-crumbs/board-crumbs.ts +56 -0
  7. package/src/board/board-crumbs/board-crumbs.tsx +108 -0
  8. package/src/board/board-data/board-data.ts +31 -5
  9. package/src/board/board-data/board-data.types.ts +10 -1
  10. package/src/board/board-kanban/board-kanban.ts +63 -9
  11. package/src/board/board-kanban/board-kanban.tsx +409 -66
  12. package/src/board/board-open-task/board-open-task.ts +32 -0
  13. package/src/board/board-panel/board-panel.tsx +202 -31
  14. package/src/board/board-settings/board-settings.tsx +209 -0
  15. package/src/board/board-stripes/board-stripes.ts +128 -0
  16. package/src/board/board-table/board-table.ts +23 -105
  17. package/src/board/board-table/board-table.tsx +436 -135
  18. package/src/board/board-task/board-task.ts +105 -0
  19. package/src/board/board-task/board-task.tsx +396 -0
  20. package/src/board/board-title-row/board-title-row.tsx +68 -0
  21. package/src/file-preview/file-preview-view.tsx +28 -0
  22. package/src/file-preview/file-preview.tsx +28 -0
  23. package/src/file-preview/pdf-file-preview.tsx +5 -0
  24. package/src/file-preview/presentation-file-preview.tsx +21 -0
  25. package/src/file-preview/spreadsheet-file-preview.tsx +5 -0
  26. package/src/file-preview/word-file-preview.tsx +5 -0
  27. package/src/i18n/de.json +81 -7
  28. package/src/i18n/en.json +81 -7
  29. package/src/i18n/es.json +81 -7
  30. package/src/nodes/nodes.tsx +26 -52
  31. package/src/resource-menu/resource-menu.tsx +18 -12
  32. package/src/router/selection-search.ts +17 -2
  33. package/src/styles.css +29 -25
  34. package/src/title-row/title-row.tsx +33 -6
  35. package/src/user-name/user-name.ts +17 -0
  36. package/vite.config.ts +111 -3
package/src/styles.css CHANGED
@@ -44,20 +44,24 @@
44
44
  grid of achromatic dots codes nothing, and neither does a graph whose blocked node looks like
45
45
  its neighbour.
46
46
 
47
- ⚠️ A step of the ramp belongs to a POSITION, never to a status id. A board's status list is
48
- configured per board renamed, extended, reordered (`board_configure`) so `done` is not a
49
- colour any more than `column four` is a name. The n-th status takes the n-th step and the ramp
50
- wraps; `archived` is the exception and always takes `--muted-foreground`, because a shelf is
51
- not a stage (see `board-status.ts`).
47
+ ⚠️ **A step belongs to a FAMILY, not to a status and not to a depth.** Every card descending
48
+ from the same root task takes the same step, whatever column it sits in and however deep it
49
+ hangs that is what lets somebody see that the card in `Backlog` belongs to the one in
50
+ `In progress`. Depth is carried by the NUMBER of stripes, never by the hue: two channels for
51
+ two questions.
52
52
 
53
- ⚠️ Colour is never the only channel. Every place these are used names the status in text as
54
- well, so a reader who does not see the difference reads it instead. */
55
- --board-status-1: oklch(0.58 0.02 260);
56
- --board-status-2: oklch(0.55 0.16 255);
57
- --board-status-3: oklch(0.65 0.15 65);
58
- --board-status-4: oklch(0.55 0.14 150);
59
- --board-status-5: oklch(0.53 0.18 305);
60
- --board-status-6: oklch(0.56 0.15 20);
53
+ ⚠️ **Six, and then they repeat.** Two families sharing a step is not a fault. At twenty root
54
+ tasks every other answer is a palette nobody can tell apart, and a colour that cannot be told
55
+ apart says less than none.
56
+
57
+ ⚠️ Colour is never the only channel. Every card that carries stripes also names its level and
58
+ where its parent sits, in text, so a reader who does not see the difference reads it instead. */
59
+ --board-family-1: oklch(0.58 0.02 260);
60
+ --board-family-2: oklch(0.55 0.16 255);
61
+ --board-family-3: oklch(0.65 0.15 65);
62
+ --board-family-4: oklch(0.55 0.14 150);
63
+ --board-family-5: oklch(0.53 0.18 305);
64
+ --board-family-6: oklch(0.56 0.15 20);
61
65
  }
62
66
 
63
67
  .dark {
@@ -90,12 +94,12 @@
90
94
  --sidebar-ring: oklch(0.556 0 0);
91
95
  /* The same six steps, lifted for a dark surface. Same hues, same order — a board must not change
92
96
  which colour a column has when the reader switches the appearance. */
93
- --board-status-1: oklch(0.72 0.02 260);
94
- --board-status-2: oklch(0.7 0.14 255);
95
- --board-status-3: oklch(0.78 0.13 65);
96
- --board-status-4: oklch(0.72 0.13 150);
97
- --board-status-5: oklch(0.7 0.16 305);
98
- --board-status-6: oklch(0.71 0.14 20);
97
+ --board-family-1: oklch(0.72 0.02 260);
98
+ --board-family-2: oklch(0.7 0.14 255);
99
+ --board-family-3: oklch(0.78 0.13 65);
100
+ --board-family-4: oklch(0.72 0.13 150);
101
+ --board-family-5: oklch(0.7 0.16 305);
102
+ --board-family-6: oklch(0.71 0.14 20);
99
103
  }
100
104
 
101
105
  @theme inline {
@@ -130,12 +134,12 @@
130
134
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
131
135
  --color-sidebar-border: var(--sidebar-border);
132
136
  --color-sidebar-ring: var(--sidebar-ring);
133
- --color-board-status-1: var(--board-status-1);
134
- --color-board-status-2: var(--board-status-2);
135
- --color-board-status-3: var(--board-status-3);
136
- --color-board-status-4: var(--board-status-4);
137
- --color-board-status-5: var(--board-status-5);
138
- --color-board-status-6: var(--board-status-6);
137
+ --color-board-family-1: var(--board-family-1);
138
+ --color-board-family-2: var(--board-family-2);
139
+ --color-board-family-3: var(--board-family-3);
140
+ --color-board-family-4: var(--board-family-4);
141
+ --color-board-family-5: var(--board-family-5);
142
+ --color-board-family-6: var(--board-family-6);
139
143
  }
140
144
 
141
145
  @layer base {
@@ -67,14 +67,36 @@ export function TitleRowScrollArea({
67
67
  */
68
68
  export function TitleRow({
69
69
  title,
70
+ crumbs,
70
71
  description,
71
72
  target,
72
73
  separated,
73
74
  children,
74
75
  }: {
76
+ /**
77
+ * ⚠️ **Not drawn while `crumbs` is set** — the path names the thing then, and nothing falls back
78
+ * to this. It stays required all the same, so every caller has to state in one word what the row
79
+ * is about: the next reader of this call site should not have to follow a component two files
80
+ * away to find out.
81
+ */
75
82
  title: string;
83
+ /**
84
+ * A path that stands in for the name.
85
+ *
86
+ * ⚠️ **It REPLACES the heading, it does not join it.** An open board card carries its name in the
87
+ * path already, and the same sentence twice above one another says nothing twice (#692). The row
88
+ * itself, its menu order and its slots stay exactly as they are: only what identifies the thing
89
+ * changes, because for a card the answer is "where it sits", not just "what it is called".
90
+ */
91
+ crumbs?: ReactNode;
76
92
  description?: string | null;
77
- target: ResourceTarget;
93
+ /**
94
+ * What the three-dot menu acts on. ⚠️ **Absent means NO menu**, and that is a real state rather
95
+ * than an oversight: a card whose node has not arrived yet has nothing the menu could rename,
96
+ * move or archive, and pointing it at the container instead would archive the wrong thing without
97
+ * asking (#692).
98
+ */
99
+ target?: ResourceTarget;
78
100
  /**
79
101
  * A separator that does NOT wait for a scroll.
80
102
  *
@@ -89,7 +111,8 @@ export function TitleRow({
89
111
  }) {
90
112
  const scroll = useContext(TitleRowScrollContext);
91
113
  const setScrolled = scroll?.setScrolled;
92
- const targetId = target.type === "flow" ? target.flow.id : target.node.id;
114
+ const targetId =
115
+ target === undefined ? undefined : target.type === "flow" ? target.flow.id : target.node.id;
93
116
  useEffect(() => {
94
117
  void targetId;
95
118
  setScrolled?.(false);
@@ -105,13 +128,17 @@ export function TitleRow({
105
128
  <div className="flex min-w-0 items-center gap-3">
106
129
  <div className="min-w-0">
107
130
  <div className="flex min-w-0 flex-wrap items-baseline gap-x-3">
108
- <h2 className="truncate text-lg font-semibold">{title}</h2>
109
- {target.type === "node" && target.node.kind === "folder" ? (
131
+ {crumbs === undefined ? (
132
+ <h2 className="truncate text-lg font-semibold">{title}</h2>
133
+ ) : (
134
+ crumbs
135
+ )}
136
+ {target?.type === "node" && target.node.kind === "folder" ? (
110
137
  <ResourceAccessSummary resourceId={target.node.id} ownerId={target.node.ownerId} />
111
138
  ) : null}
112
139
  {/* A flow carries its own grants since #530, so who reaches it is worth showing where
113
140
  it stands — the same question the folder above already answers here. */}
114
- {target.type === "flow" ? (
141
+ {target?.type === "flow" ? (
115
142
  <ResourceAccessSummary
116
143
  kind="flow"
117
144
  resourceId={target.flow.id}
@@ -129,7 +156,7 @@ export function TitleRow({
129
156
  <div className="flex shrink-0 items-center gap-2">
130
157
  <div data-slot="title-actions" className="flex items-center gap-2" />
131
158
  {children}
132
- <ResourceMenu target={target} variant="title" />
159
+ {target === undefined ? null : <ResourceMenu target={target} variant="title" />}
133
160
  </div>
134
161
  </div>
135
162
  );
@@ -39,3 +39,20 @@ export function useUserName(userId: string | null): string | null {
39
39
  if (userId === null || me === null || me.id !== userId) return null;
40
40
  return displayNameOf(me);
41
41
  }
42
+
43
+ /**
44
+ * The one or two letters that stand for a name in a circle.
45
+ *
46
+ * ⚠️ Lives here rather than beside the circle that first needed it: two surfaces now draw the same
47
+ * circle — the access summary and a task's assignee — and two copies of this would drift into two
48
+ * different abbreviations of the same person.
49
+ */
50
+ export function initials(value: string): string {
51
+ const parts = value
52
+ .replace(/@.*$/, "")
53
+ .split(/[\s._+-]+/)
54
+ .filter(Boolean);
55
+ if (parts.length === 0) return "?";
56
+ if (parts.length === 1) return (parts[0]?.slice(0, 2) ?? "?").toUpperCase();
57
+ return `${parts[0]?.[0] ?? ""}${parts.at(-1)?.[0] ?? ""}`.toUpperCase();
58
+ }
package/vite.config.ts CHANGED
@@ -1,11 +1,119 @@
1
+ import { copyFileSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, resolve } from "node:path";
1
4
  import { fileURLToPath } from "node:url";
5
+ import { fileViewerRenderers } from "@file-viewer/vite-plugin";
2
6
  import tailwindcss from "@tailwindcss/vite";
3
7
  import react from "@vitejs/plugin-react";
4
- import { defineConfig } from "vite";
8
+ import { defineConfig, type Plugin } from "vite";
9
+
10
+ const require = createRequire(import.meta.url);
11
+
12
+ function fileViewerLicenses(): Plugin {
13
+ let outDir = "dist";
14
+ return {
15
+ name: "file-viewer-licenses",
16
+ configResolved(config) {
17
+ outDir = config.build.outDir;
18
+ },
19
+ writeBundle() {
20
+ const target = resolve(outDir, "licenses/file-viewer");
21
+ const reactPackage = require.resolve("@file-viewer/react/package.json");
22
+ const rendererPdfPackage = require.resolve("@file-viewer/renderer-pdf/package.json");
23
+ const rendererPresentationPackage = require.resolve(
24
+ "@file-viewer/renderer-presentation/package.json",
25
+ );
26
+ const rendererWordPackage = require.resolve("@file-viewer/renderer-word/package.json");
27
+ const packageLicenses = [
28
+ ["@file-viewer/react", reactPackage],
29
+ [
30
+ "@file-viewer/core",
31
+ createRequire(reactPackage).resolve("@file-viewer/core/package.json"),
32
+ ],
33
+ [
34
+ "@file-viewer/doc",
35
+ createRequire(rendererWordPackage).resolve("@file-viewer/doc/package.json"),
36
+ ],
37
+ [
38
+ "@file-viewer/docx",
39
+ createRequire(rendererWordPackage).resolve("@file-viewer/docx/package.json"),
40
+ ],
41
+ [
42
+ "@file-viewer/ppt",
43
+ resolve(
44
+ dirname(createRequire(rendererPresentationPackage).resolve("@file-viewer/ppt")),
45
+ "package.json",
46
+ ),
47
+ ],
48
+ [
49
+ "@file-viewer/pptx",
50
+ createRequire(rendererPresentationPackage).resolve("@file-viewer/pptx/package.json"),
51
+ ],
52
+ ["@file-viewer/renderer-pdf", rendererPdfPackage],
53
+ ["@file-viewer/renderer-presentation", rendererPresentationPackage],
54
+ [
55
+ "@file-viewer/renderer-spreadsheet",
56
+ require.resolve("@file-viewer/renderer-spreadsheet/package.json"),
57
+ ],
58
+ ["@file-viewer/renderer-word", rendererWordPackage],
59
+ ["@file-viewer/vite-plugin", require.resolve("@file-viewer/vite-plugin/package.json")],
60
+ ["pdfjs-dist", createRequire(rendererPdfPackage).resolve("pdfjs-dist/package.json")],
61
+ ] as const;
62
+ mkdirSync(target, { recursive: true });
63
+ for (const [name, packageJson] of packageLicenses) {
64
+ copyFileSync(
65
+ resolve(dirname(packageJson), "LICENSE"),
66
+ resolve(target, `${name.replace("@file-viewer/", "")}.LICENSE`),
67
+ );
68
+ }
69
+ writeFileSync(
70
+ resolve(target, "THIRD_PARTY_NOTICES.txt"),
71
+ [
72
+ "File Viewer packages used by Intel",
73
+ "",
74
+ "Apache-2.0:",
75
+ "@file-viewer/react",
76
+ "@file-viewer/core",
77
+ "@file-viewer/docx",
78
+ "@file-viewer/pptx",
79
+ "@file-viewer/renderer-pdf",
80
+ "@file-viewer/renderer-presentation",
81
+ "@file-viewer/renderer-spreadsheet",
82
+ "@file-viewer/renderer-word",
83
+ "@file-viewer/vite-plugin",
84
+ "pdfjs-dist",
85
+ "",
86
+ "MIT:",
87
+ "@file-viewer/doc",
88
+ "",
89
+ "Package-specific license (bundled transitively, not exposed by Intel; see ppt.LICENSE):",
90
+ "@file-viewer/ppt",
91
+ "",
92
+ "The complete license text for every package is stored beside this notice.",
93
+ "",
94
+ ].join("\n"),
95
+ );
96
+ },
97
+ };
98
+ }
5
99
 
6
100
  export default defineConfig({
7
- plugins: [react(), tailwindcss()],
8
- resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) } },
101
+ plugins: [
102
+ react(),
103
+ tailwindcss(),
104
+ fileViewerRenderers({
105
+ formats: ["pdf", "docx", "xlsx", "pptx"],
106
+ inject: false,
107
+ chunkStrategy: "renderer",
108
+ }),
109
+ fileViewerLicenses(),
110
+ ],
111
+ resolve: {
112
+ alias: {
113
+ "@": fileURLToPath(new URL("./src", import.meta.url)),
114
+ stream: "readable-stream",
115
+ },
116
+ },
9
117
  server: {
10
118
  proxy: {
11
119
  "/api": { target: "http://localhost:8788", changeOrigin: true },