This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new aab7f16c389 Add resize function for Dag Documentation (#56344)
aab7f16c389 is described below

commit aab7f16c3897ea1447d7829cc8f4d2aaf1ba5142
Author: LI,JHE-CHEN <[email protected]>
AuthorDate: Thu Oct 16 09:13:24 2025 -0400

    Add resize function for Dag Documentation (#56344)
    
    * feat: Add dialog resize function
    
    * feat: Memorize resize state
    
    * refactor: replace inline style and use useLocalStorage
    
    * fix: remove redundent style
    
    * refactor: optimize state management
---
 airflow-core/src/airflow/ui/package.json           |  2 +
 airflow-core/src/airflow/ui/pnpm-lock.yaml         | 45 ++++++++++++
 .../ui/src/components/DisplayMarkdownButton.tsx    | 21 +++---
 .../ui/src/components/ui/ResizableWrapper.tsx      | 79 ++++++++++++++++++++++
 .../ui/src/utils/usePersistentResizableState.ts    | 41 +++++++++++
 5 files changed, 180 insertions(+), 8 deletions(-)

diff --git a/airflow-core/src/airflow/ui/package.json 
b/airflow-core/src/airflow/ui/package.json
index 2820cd7eb2b..88300c3f147 100644
--- a/airflow-core/src/airflow/ui/package.json
+++ b/airflow-core/src/airflow/ui/package.json
@@ -27,6 +27,7 @@
     "@tanstack/react-table": "^8.21.3",
     "@tanstack/react-virtual": "^3.13.8",
     "@types/debounce-promise": "^3.1.9",
+    "@types/react-resizable": "^3.0.8",
     "@uiw/codemirror-themes-all": "^4.23.12",
     "@uiw/react-codemirror": "^4.23.12",
     "@visx/group": "^3.12.0",
@@ -57,6 +58,7 @@
     "react-innertext": "^1.1.5",
     "react-json-view": "^1.21.3",
     "react-markdown": "^9.1.0",
+    "react-resizable": "^3.0.5",
     "react-resizable-panels": "^2.1.7",
     "react-router-dom": "^6.30.0",
     "react-syntax-highlighter": "^15.6.1",
diff --git a/airflow-core/src/airflow/ui/pnpm-lock.yaml 
b/airflow-core/src/airflow/ui/pnpm-lock.yaml
index 065da9e6109..fb491d789f2 100644
--- a/airflow-core/src/airflow/ui/pnpm-lock.yaml
+++ b/airflow-core/src/airflow/ui/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
       '@types/debounce-promise':
         specifier: ^3.1.9
         version: 3.1.9
+      '@types/react-resizable':
+        specifier: ^3.0.8
+        version: 3.0.8
       '@uiw/codemirror-themes-all':
         specifier: ^4.23.12
         version: 
4.23.12(@codemirror/[email protected])(@codemirror/[email protected])(@codemirror/[email protected])
@@ -122,6 +125,9 @@ importers:
       react-markdown:
         specifier: ^9.1.0
         version: 9.1.0(@types/[email protected])([email protected])
+      react-resizable:
+        specifier: ^3.0.5
+        version: 3.0.5([email protected]([email protected]))([email protected])
       react-resizable-panels:
         specifier: ^2.1.7
         version: 2.1.7([email protected]([email protected]))([email protected])
@@ -1243,6 +1249,9 @@ packages:
     peerDependencies:
       '@types/react': ^18.0.0
 
+  '@types/[email protected]':
+    resolution: {integrity: 
sha512-Pcvt2eGA7KNXldt1hkhVhAgZ8hK41m0mp89mFgQi7LAAEZiaLgm4fHJ5zbJZ/4m2LVaAyYrrRRv1LHDcrGQanA==}
+
   '@types/[email protected]':
     resolution: {integrity: 
sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==}
 
@@ -2097,6 +2106,10 @@ packages:
     resolution: {integrity: 
sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
     engines: {node: '>=12'}
 
+  [email protected]:
+    resolution: {integrity: 
sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+    engines: {node: '>=6'}
+
   [email protected]:
     resolution: {integrity: 
sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==}
 
@@ -3821,6 +3834,12 @@ packages:
     peerDependencies:
       react: ^19.1.1
 
+  [email protected]:
+    resolution: {integrity: 
sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==}
+    peerDependencies:
+      react: '>= 16.3.0'
+      react-dom: '>= 16.3.0'
+
   [email protected]:
     resolution: {integrity: 
sha512-vpfuHuQMF/L6GpuQ4c3ZDo+pRYxIi40gQqsCmmfUBwm+oqvBhKhwghCuj2o00YCgSfU6bR9KC/xnQGWm3Gr08A==}
     engines: {node: '>=18.0.0'}
@@ -3887,6 +3906,11 @@ packages:
       react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
       react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
 
+  [email protected]:
+    resolution: {integrity: 
sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==}
+    peerDependencies:
+      react: '>= 16.3'
+
   [email protected]:
     resolution: {integrity: 
sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==}
     engines: {node: '>=14.0.0'}
@@ -5728,6 +5752,10 @@ snapshots:
     dependencies:
       '@types/react': 18.3.19
 
+  '@types/[email protected]':
+    dependencies:
+      '@types/react': 18.3.19
+
   '@types/[email protected]':
     dependencies:
       '@types/react': 18.3.19
@@ -7241,6 +7269,8 @@ snapshots:
       strip-ansi: 6.0.1
       wrap-ansi: 7.0.0
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected](@lezer/[email protected]):
@@ -9359,6 +9389,13 @@ snapshots:
       react: 19.1.1
       scheduler: 0.26.0
 
+  [email protected]([email protected]([email protected]))([email protected]):
+    dependencies:
+      clsx: 2.1.1
+      prop-types: 15.8.1
+      react: 19.1.1
+      react-dom: 19.1.1([email protected])
+
   [email protected]([email protected]):
     dependencies:
       react: 19.1.1
@@ -9428,6 +9465,14 @@ snapshots:
       react: 19.1.1
       react-dom: 19.1.1([email protected])
 
+  [email protected]([email protected]([email protected]))([email protected]):
+    dependencies:
+      prop-types: 15.8.1
+      react: 19.1.1
+      react-draggable: 4.5.0([email protected]([email protected]))([email protected])
+    transitivePeerDependencies:
+      - react-dom
+
   [email protected]([email protected]([email protected]))([email protected]):
     dependencies:
       '@remix-run/router': 1.23.0
diff --git 
a/airflow-core/src/airflow/ui/src/components/DisplayMarkdownButton.tsx 
b/airflow-core/src/airflow/ui/src/components/DisplayMarkdownButton.tsx
index 89bdd03f837..3eab57b6581 100644
--- a/airflow-core/src/airflow/ui/src/components/DisplayMarkdownButton.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DisplayMarkdownButton.tsx
@@ -20,9 +20,12 @@ import { Box, Heading, VStack } from "@chakra-ui/react";
 import { type ReactElement, useState } from "react";
 
 import { Button, Dialog } from "src/components/ui";
+import { ResizableWrapper } from "src/components/ui/ResizableWrapper";
 
 import ReactMarkdown from "./ReactMarkdown";
 
+const STORAGE_KEY = "airflow-markdown-dialog-size";
+
 const DisplayMarkdownButton = ({
   header,
   icon,
@@ -48,14 +51,16 @@ const DisplayMarkdownButton = ({
         open={isDocsOpen}
         size="md"
       >
-        <Dialog.Content backdrop>
-          <Dialog.Header bg="info.muted">
-            <Heading size="xl">{header}</Heading>
-            <Dialog.CloseTrigger closeButtonProps={{ size: "xl" }} />
-          </Dialog.Header>
-          <Dialog.Body alignItems="flex-start" as={VStack} gap="0">
-            <ReactMarkdown>{mdContent}</ReactMarkdown>
-          </Dialog.Body>
+        <Dialog.Content backdrop maxHeight="none" maxWidth="none" padding={0} 
width="auto">
+          <ResizableWrapper storageKey={STORAGE_KEY}>
+            <Dialog.Header bg="brand.muted" flexShrink={0}>
+              <Heading size="xl">{header}</Heading>
+              <Dialog.CloseTrigger closeButtonProps={{ size: "xl" }} />
+            </Dialog.Header>
+            <Dialog.Body alignItems="flex-start" as={VStack} flex="1" gap="0" 
overflow="auto">
+              <ReactMarkdown>{mdContent}</ReactMarkdown>
+            </Dialog.Body>
+          </ResizableWrapper>
         </Dialog.Content>
       </Dialog.Root>
     </Box>
diff --git a/airflow-core/src/airflow/ui/src/components/ui/ResizableWrapper.tsx 
b/airflow-core/src/airflow/ui/src/components/ui/ResizableWrapper.tsx
new file mode 100644
index 00000000000..26bdde642e0
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/ui/ResizableWrapper.tsx
@@ -0,0 +1,79 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Box } from "@chakra-ui/react";
+import { forwardRef } from "react";
+import type { ReactNode } from "react";
+import { ResizableBox } from "react-resizable";
+import "react-resizable/css/styles.css";
+
+import { usePersistentResizableState } from 
"src/utils/usePersistentResizableState";
+
+const ResizeHandle = forwardRef<HTMLDivElement>((props, ref) => (
+  <Box
+    background="linear-gradient(-45deg, transparent 6px, #ccc 6px, #ccc 8px, 
transparent 8px, transparent 12px, #ccc 12px, #ccc 14px, transparent 14px)"
+    bottom={0}
+    cursor="se-resize"
+    height={5}
+    position="absolute"
+    ref={ref}
+    right={0}
+    width={5}
+    {...props}
+  />
+));
+
+type ResizableWrapperProps = {
+  readonly children: ReactNode;
+  readonly defaultSize?: { height: number; width: number };
+  readonly maxConstraints?: [width: number, height: number];
+  readonly storageKey: string;
+};
+
+const DEFAULT_SIZE = { height: 400, width: 500 };
+const MAX_SIZE: [number, number] = [1200, 800];
+
+export const ResizableWrapper = ({
+  children,
+  defaultSize = DEFAULT_SIZE,
+  maxConstraints = MAX_SIZE,
+  storageKey,
+}: ResizableWrapperProps) => {
+  const { handleResize, handleResizeStop, size } = 
usePersistentResizableState(storageKey, defaultSize);
+
+  return (
+    <ResizableBox
+      handle={<ResizeHandle />}
+      height={size.height}
+      maxConstraints={maxConstraints}
+      minConstraints={[DEFAULT_SIZE.width, DEFAULT_SIZE.height]}
+      onResize={handleResize}
+      onResizeStop={handleResizeStop}
+      resizeHandles={["se"]}
+      style={{
+        backgroundColor: "inherit",
+        borderRadius: "inherit",
+        overflow: "hidden",
+        position: "relative",
+      }}
+      width={size.width}
+    >
+      {children}
+    </ResizableBox>
+  );
+};
diff --git 
a/airflow-core/src/airflow/ui/src/utils/usePersistentResizableState.ts 
b/airflow-core/src/airflow/ui/src/utils/usePersistentResizableState.ts
new file mode 100644
index 00000000000..6ce87970087
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/utils/usePersistentResizableState.ts
@@ -0,0 +1,41 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useCallback, useState } from "react";
+import { useLocalStorage } from "usehooks-ts";
+
+type Size = { height: number; width: number };
+
+export const usePersistentResizableState = (storageKey: string, defaultSize: 
Size) => {
+  const [storedSize, setStoredSize] = useLocalStorage(storageKey, defaultSize);
+  const [size, setSize] = useState(storedSize);
+
+  const handleResize = useCallback((_event: React.SyntheticEvent, { size: 
newSize }: { size: Size }) => {
+    setSize(newSize);
+  }, []);
+
+  const handleResizeStop = useCallback(
+    (_event: React.SyntheticEvent, { size: finalSize }: { size: Size }) => {
+      setSize(finalSize);
+      setStoredSize(finalSize);
+    },
+    [setStoredSize],
+  );
+
+  return { handleResize, handleResizeStop, size };
+};

Reply via email to