@anchrd/intel-ui 0.42.0 → 0.43.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-ui",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -105,8 +105,6 @@ export function AttachmentViewer({ document }: { document: NodeDocument }) {
105
105
  const kind = document.version
106
106
  ? attachmentPreviewKind(document.node.title, document.version.mediaType)
107
107
  : "unsupported";
108
- const hasViewerControls = kind !== "image" && kind !== "unsupported";
109
-
110
108
  return (
111
109
  <div className="flex min-h-0 flex-1 flex-col px-6 pb-6">
112
110
  <ActionSlot name="title-actions">
@@ -115,12 +113,21 @@ export function AttachmentViewer({ document }: { document: NodeDocument }) {
115
113
  aria-label={i18n.t(detailsOpen ? "attachment.detailsHide" : "attachment.detailsShow")}
116
114
  aria-expanded={detailsOpen}
117
115
  onClick={() => setDetailsOpen((current) => !current)}
118
- className="inline-flex size-8 shrink-0 items-center justify-center rounded-md bg-muted outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring"
116
+ className={cn(
117
+ "inline-flex size-8 shrink-0 items-center justify-center rounded-md bg-transparent outline-none hover:bg-accent focus-visible:bg-accent focus-visible:ring-2 focus-visible:ring-ring",
118
+ detailsOpen && "bg-accent",
119
+ )}
119
120
  >
120
121
  <Info aria-hidden="true" className="size-4" />
121
122
  </button>
122
123
  </ActionSlot>
123
- <div className="relative flex min-h-0 flex-1 overflow-hidden rounded-xl border bg-background">
124
+ <div
125
+ data-preview-kind={kind}
126
+ className={cn(
127
+ "relative flex min-h-0 flex-1 overflow-hidden rounded-xl",
128
+ kind === "image" ? "bg-muted/40" : "border bg-background",
129
+ )}
130
+ >
124
131
  <div className="relative min-h-0 min-w-0 flex-1 bg-muted/40">
125
132
  {document.version === null ? (
126
133
  <PreviewMessage message={i18n.t("attachment.empty")} />
@@ -147,17 +154,10 @@ export function AttachmentViewer({ document }: { document: NodeDocument }) {
147
154
  <AttachmentDetails
148
155
  document={document}
149
156
  file={file}
150
- footerInset={hasViewerControls}
151
157
  kind={kind}
152
158
  close={() => setDetailsOpen(false)}
153
159
  />
154
160
  ) : null}
155
- {detailsOpen && hasViewerControls ? (
156
- <div
157
- aria-hidden="true"
158
- className="pointer-events-none absolute inset-x-0 bottom-14 z-10 hidden border-t md:block"
159
- />
160
- ) : null}
161
161
  </div>
162
162
  </div>
163
163
  );
@@ -212,13 +212,11 @@ function ImagePreview({ file, title }: { file: File; title: string }) {
212
212
  function AttachmentDetails({
213
213
  document,
214
214
  file,
215
- footerInset,
216
215
  kind,
217
216
  close,
218
217
  }: {
219
218
  document: NodeDocument;
220
219
  file: File | null;
221
- footerInset: boolean;
222
220
  kind: AttachmentPreviewKind;
223
221
  close(): void;
224
222
  }) {
@@ -257,11 +255,7 @@ function AttachmentDetails({
257
255
  return (
258
256
  <aside
259
257
  aria-label={i18n.t("attachment.details.title")}
260
- className={cn(
261
- "absolute inset-x-0 bottom-0 z-10 max-h-[80%] overflow-auto border-t bg-background shadow-lg md:static md:z-auto md:w-80 md:max-h-none md:shrink-0 md:border-l md:border-t-0 md:shadow-none",
262
- // File renderers keep their controls in a shared footer; the panel ends beside that footer.
263
- footerInset && "md:mb-14",
264
- )}
258
+ className="absolute inset-x-0 bottom-0 z-10 max-h-[80%] overflow-auto border-t bg-background shadow-lg md:static md:z-auto md:w-80 md:max-h-none md:shrink-0 md:border-l md:border-t-0 md:shadow-none"
265
259
  >
266
260
  <div className="flex items-center justify-between border-b px-5 py-4">
267
261
  <h3 className="font-semibold">{i18n.t("attachment.details.title")}</h3>
@@ -35,12 +35,14 @@ const viewerTheme = `
35
35
  }
36
36
  .pdf-shell,.pdf-wrapper{background:var(--muted)!important;color:var(--foreground)!important}
37
37
  .pdf-nav-pane,.pdf-nav-tabs,.pdf-nav-head{background:var(--background)!important;border-color:var(--border)!important}
38
- .pdf-nav-head{display:none!important}.pdf-nav-tabs{display:flex!important;justify-content:flex-end;padding:6px!important}
39
- .pdf-nav-tabs button{width:30px;min-width:30px;padding:0;font-size:0;color:var(--muted-foreground)!important}
38
+ @media (min-width:721px){.pdf-shell:not(.pdf-shell--nav-hidden) .pdf-content{grid-template-columns:6rem minmax(0,1fr)!important}}
39
+ .pdf-nav-head{display:none!important}.pdf-nav-tabs{display:flex!important;justify-content:center;padding:6px!important}
40
+ .pdf-nav-tabs:not([data-outline-available="true"]){display:none!important}.pdf-nav-tabs button.active{display:none!important}
41
+ .pdf-nav-tabs button{width:30px;min-width:30px;padding:0;border:0!important;box-shadow:none!important;font-size:0;color:var(--muted-foreground)!important}
40
42
  .pdf-nav-tabs button svg{width:15px;height:15px;margin:auto;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
41
- .pdf-nav-tabs button:hover,.pdf-nav-tabs button.active{background:var(--accent)!important;border-color:var(--border)!important;color:var(--accent-foreground)!important}
42
- .pdf-page-list{gap:4px!important;padding:6px!important}.pdf-page-button{display:flex!important;min-height:36px!important;padding:4px!important;border-color:transparent!important;background:transparent!important;color:var(--foreground)!important}
43
- .pdf-page-button:hover,.pdf-page-button--active{background:var(--accent)!important;border-color:var(--border)!important;box-shadow:none!important}
43
+ .pdf-nav-tabs button:hover,.pdf-nav-tabs button.active{background:var(--accent)!important;border:0!important;color:var(--accent-foreground)!important}
44
+ .pdf-page-list{gap:4px!important;padding:6px!important}.pdf-page-button{display:flex!important;width:32px!important;min-width:32px!important;height:32px!important;min-height:32px!important;margin-inline:auto!important;padding:0!important;border:0!important;box-shadow:none!important;background:transparent!important;color:var(--foreground)!important;justify-content:center!important}
45
+ .pdf-page-button:hover,.pdf-page-button--active{background:var(--accent)!important;border:0!important;box-shadow:none!important}
44
46
  .pdf-page-thumb{width:28px!important;height:28px!important;border:0!important;background:transparent!important;color:var(--foreground)!important}
45
47
  .pdf-page-label{display:none!important}.pdf-outline-button{color:var(--foreground)!important}.pdf-outline-button:hover{background:var(--accent)!important;border-color:var(--border)!important}
46
48
  `;
@@ -92,35 +94,83 @@ export function FilePreviewView({
92
94
  style.dataset.intelViewerTheme = "true";
93
95
  style.textContent = viewerTheme;
94
96
  root.append(style);
95
- const labelPages = () => {
97
+ const adaptNavigation = () => {
96
98
  for (const button of root.querySelectorAll<HTMLButtonElement>(".pdf-page-button")) {
97
99
  const label = button.querySelector(".pdf-page-label")?.textContent?.trim();
98
100
  if (label) button.title = label;
99
101
  }
100
- for (const [index, button] of root
101
- .querySelectorAll<HTMLButtonElement>(".pdf-nav-tabs button")
102
- .entries()) {
103
- if (button.querySelector("[data-intel-navigation-icon]")) continue;
104
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
105
- svg.dataset.intelNavigationIcon = "true";
106
- svg.setAttribute("viewBox", "0 0 24 24");
107
- svg.setAttribute("aria-hidden", "true");
108
- const paths =
109
- index === 0
110
- ? ["M8 6h13M8 12h13M8 18h13", "M3 6h.01M3 12h.01M3 18h.01"]
111
- : ["M4 6h7M9 12h11M13 18h7", "M4 6v12h9M9 6v6M13 12v6"];
112
- for (const pathData of paths) {
113
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
114
- path.setAttribute("d", pathData);
115
- svg.append(path);
102
+ const tabs = root.querySelector<HTMLElement>(".pdf-nav-tabs");
103
+ const buttons = tabs?.querySelectorAll<HTMLButtonElement>("button");
104
+ if (tabs && buttons?.length === 2) {
105
+ tabs.setAttribute("role", "group");
106
+ for (const [index, button] of buttons.entries()) {
107
+ button.setAttribute("role", "button");
108
+ button.removeAttribute("aria-selected");
109
+ if (!button.querySelector("[data-intel-navigation-icon]")) {
110
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
111
+ svg.dataset.intelNavigationIcon = "true";
112
+ svg.setAttribute("viewBox", "0 0 24 24");
113
+ svg.setAttribute("aria-hidden", "true");
114
+ const paths =
115
+ index === 0
116
+ ? ["M8 6h13M8 12h13M8 18h13", "M3 6h.01M3 12h.01M3 18h.01"]
117
+ : ["M4 6h7M9 12h11M13 18h7", "M4 6v12h9M9 6v6M13 12v6"];
118
+ for (const pathData of paths) {
119
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
120
+ path.setAttribute("d", pathData);
121
+ svg.append(path);
122
+ }
123
+ button.prepend(svg);
124
+ }
125
+ if (button.dataset.intelNavigationAdapted !== "true") {
126
+ button.dataset.intelNavigationAdapted = "true";
127
+ button.addEventListener("click", () => {
128
+ if (root.activeElement === button) buttons[index === 0 ? 1 : 0]?.focus();
129
+ for (const tabButton of buttons) tabButton.removeAttribute("aria-selected");
130
+ });
131
+ }
116
132
  }
117
- button.prepend(svg);
118
133
  }
119
134
  };
120
- labelPages();
121
- const observer = new MutationObserver(labelPages);
135
+ let outlineObserver: MutationObserver;
136
+ let outlineTarget: HTMLElement | null = null;
137
+ const probeOutline = () => {
138
+ const tabs = root.querySelector<HTMLElement>(".pdf-nav-tabs");
139
+ const [pagesTab, outlineTab] = tabs?.querySelectorAll<HTMLButtonElement>("button") ?? [];
140
+ const navPane = root.querySelector<HTMLElement>(".pdf-nav-pane");
141
+ const navList = root.querySelector<HTMLElement>(".pdf-page-list,.pdf-outline-list");
142
+ if (!tabs || !pagesTab || !outlineTab || !navPane || !navList) return;
143
+
144
+ outlineObserver.disconnect();
145
+ const startedInOutline = outlineTab.classList.contains("active");
146
+ if (!startedInOutline) {
147
+ navPane.style.visibility = "hidden";
148
+ outlineTab.click();
149
+ }
150
+ tabs.dataset.outlineAvailable = String(root.querySelector(".pdf-outline-button") !== null);
151
+ if (!startedInOutline) pagesTab.click();
152
+ navPane.style.removeProperty("visibility");
153
+ adaptNavigation();
154
+ outlineTarget = navList;
155
+ outlineObserver.observe(navList, { childList: true });
156
+ };
157
+ adaptNavigation();
158
+ const observer = new MutationObserver(() => {
159
+ adaptNavigation();
160
+ if (!outlineTarget || !root.contains(outlineTarget)) probeOutline();
161
+ });
122
162
  observer.observe(root, { childList: true, subtree: true });
123
- return () => observer.disconnect();
163
+ outlineObserver = new MutationObserver(() => {
164
+ const tabs = root.querySelector<HTMLElement>(".pdf-nav-tabs");
165
+ if (tabs?.dataset.outlineAvailable !== "true") probeOutline();
166
+ });
167
+ // The pinned renderer exposes no outline count. Probe synchronously between paints, then let
168
+ // direct navigation-list mutations trigger another probe whenever a late outline read lands.
169
+ probeOutline();
170
+ return () => {
171
+ observer.disconnect();
172
+ outlineObserver.disconnect();
173
+ };
124
174
  }, [state?.ready]);
125
175
 
126
176
  useEffect(() => {
@@ -152,7 +202,7 @@ export function FilePreviewView({
152
202
  </button>
153
203
  </ActionSlot>
154
204
  ) : null}
155
- <div className="absolute inset-x-0 top-0 bottom-14 min-h-0">
205
+ <div className="absolute inset-0 min-h-0">
156
206
  <FileViewer
157
207
  ref={viewer}
158
208
  file={file}
@@ -165,7 +215,11 @@ export function FilePreviewView({
165
215
  <div
166
216
  role="toolbar"
167
217
  aria-label={i18n.t("attachment.viewer.controls")}
168
- className="absolute inset-x-3 bottom-2 z-[5] mx-auto flex min-h-10 w-fit max-w-[calc(100%-1.5rem)] items-center gap-1 rounded-full border bg-background/95 p-1 shadow-lg backdrop-blur"
218
+ className={`absolute right-3 bottom-2 left-3 z-[5] mx-auto flex min-h-10 w-fit max-w-[calc(100%-1.5rem)] items-center gap-1 rounded-full border bg-background/80 p-1 shadow-lg backdrop-blur-md ${
219
+ viewState?.navigation?.visible
220
+ ? "[@media(max-width:720px)]:hidden min-[721px]:left-[calc(6rem+0.75rem)]"
221
+ : ""
222
+ }`}
169
223
  >
170
224
  <span role="status" aria-live="polite" aria-atomic="true" className="sr-only">
171
225
  {search?.query