mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 72a98644cb | |||
| 3c60757c13 | |||
| 785a241759 | |||
| b99b30eb51 | |||
| 7ec1a75229 | |||
| c2d5f20d14 | |||
| 5d2a748330 | |||
| e991b616b8 | |||
| 2d3703a215 | |||
| 640ffd3845 | |||
| aefefa8c9d | |||
| dc0eb91902 | |||
| f4d7697749 | |||
| 9f6e17e728 | |||
| 19e3f123f7 | |||
| 6638dfd67b | |||
| 8fb831fac6 | |||
| 7c2268fb81 | |||
| 466a659ef2 | |||
| 5d62613fb8 |
@@ -1,6 +1,7 @@
|
||||
body {
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#storybook-root {
|
||||
|
||||
@@ -3,6 +3,9 @@ import type { Preview } from "@storybook/react";
|
||||
import React from "react";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import "../frontend/app/theme.less";
|
||||
import "../frontend/app/app.less";
|
||||
import "../frontend/app/reset.less";
|
||||
import "./global.css";
|
||||
|
||||
const preview: Preview = {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Building for release
|
||||
|
||||
## Temporary instructions while waiting on GitHub support
|
||||
|
||||
1. On the `wave8` branch, run `task version -- <none, patch, minor, major> <false, true>` based on the options mentioned in step 2 of the [Step-by-step guide](#step-by-step-guide).
|
||||
2. Commit and push the changes to the `wave8` branch.
|
||||
3. Manually create a tag named `v<version>` attached to `origin/wave8`. Push the tag to GitHub.
|
||||
4. This should kick off the build as normal and create a draft release when done. Continue from step 4 of the [Step-by-step guide](#step-by-step-guide).
|
||||
|
||||
## Step-by-step guide
|
||||
|
||||
1. Go to the [Actions tab](https://github.com/wavetermdev/waveterm/actions) and select "Bump Version" from the left sidebar.
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
unregisterBlockComponentModel,
|
||||
} from "@/store/global";
|
||||
import * as WOS from "@/store/wos";
|
||||
import { getElemAsStr } from "@/util/focusutil";
|
||||
import { focusedBlockId, getElemAsStr } from "@/util/focusutil";
|
||||
import * as util from "@/util/util";
|
||||
import { CpuPlotView, CpuPlotViewModel, makeCpuPlotViewModel } from "@/view/cpuplot/cpuplot";
|
||||
import { HelpView, HelpViewModel, makeHelpViewModel } from "@/view/helpview/helpview";
|
||||
@@ -159,12 +159,11 @@ const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
|
||||
return;
|
||||
}
|
||||
setBlockClicked(false);
|
||||
const focusWithin = blockRef.current?.contains(document.activeElement);
|
||||
const focusWithin = focusedBlockId() == nodeModel.blockId;
|
||||
if (!focusWithin) {
|
||||
setFocusTarget();
|
||||
}
|
||||
if (!isFocused) {
|
||||
console.log("blockClicked focus", nodeModel.blockId);
|
||||
nodeModel.focusNode();
|
||||
}
|
||||
}, [blockClicked, isFocused]);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { fn } from "@storybook/test";
|
||||
import { Button } from "./button";
|
||||
|
||||
const meta = {
|
||||
title: "Elements/Button",
|
||||
component: Button,
|
||||
args: {
|
||||
children: "Click Me",
|
||||
disabled: false,
|
||||
className: "",
|
||||
onClick: fn(),
|
||||
},
|
||||
argTypes: {
|
||||
onClick: {
|
||||
action: "clicked",
|
||||
description: "Click event handler",
|
||||
},
|
||||
children: {
|
||||
description: "Content inside the button",
|
||||
},
|
||||
disabled: {
|
||||
description: "Disables the button if true",
|
||||
},
|
||||
className: {
|
||||
description: "Additional class names to style the button",
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabled: true,
|
||||
children: "Disabled Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const GreySolid: Story = {
|
||||
args: {
|
||||
className: "solid grey",
|
||||
children: "Grey Solid Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const RedSolid: Story = {
|
||||
args: {
|
||||
className: "solid red",
|
||||
children: "Red Solid Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const YellowSolid: Story = {
|
||||
args: {
|
||||
className: "solid yellow",
|
||||
children: "Yellow Solid Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const GreenOutlined: Story = {
|
||||
args: {
|
||||
className: "outlined green",
|
||||
children: "Green Outline Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const GreyOutlined: Story = {
|
||||
args: {
|
||||
className: "outlined grey",
|
||||
children: "Grey Outline Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const RedOutlined: Story = {
|
||||
args: {
|
||||
className: "outlined red",
|
||||
children: "Red Outline Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const YellowOutlined: Story = {
|
||||
args: {
|
||||
className: "outlined yellow",
|
||||
children: "Yellow Outline Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const GreenGhostText: Story = {
|
||||
args: {
|
||||
className: "ghost green",
|
||||
children: "Yellow Ghost Text Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const GreyGhostText: Story = {
|
||||
args: {
|
||||
className: "ghost grey",
|
||||
children: "Grey Ghost Text Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const RedGhost: Story = {
|
||||
args: {
|
||||
className: "ghost red",
|
||||
children: "Red Ghost Text Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const YellowGhostText: Story = {
|
||||
args: {
|
||||
className: "ghost yellow",
|
||||
children: "Yellow Ghost Text Button",
|
||||
},
|
||||
};
|
||||
@@ -9,41 +9,39 @@ import "./button.less";
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
target?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
const Button = memo(
|
||||
forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ children, disabled, source, className = "", ...props }: ButtonProps, ref) => {
|
||||
const btnRef = useRef<HTMLButtonElement>(null);
|
||||
useImperativeHandle(ref, () => btnRef.current as HTMLButtonElement);
|
||||
forwardRef<HTMLButtonElement, ButtonProps>(({ children, disabled, className = "", ...props }: ButtonProps, ref) => {
|
||||
const btnRef = useRef<HTMLButtonElement>(null);
|
||||
useImperativeHandle(ref, () => btnRef.current as HTMLButtonElement);
|
||||
|
||||
const childrenArray = Children.toArray(children);
|
||||
const childrenArray = Children.toArray(children);
|
||||
|
||||
// Check if the className contains any of the categories: solid, outlined, or ghost
|
||||
const containsButtonCategory = /(solid|outline|ghost)/.test(className);
|
||||
// If no category is present, default to 'solid'
|
||||
const categoryClassName = containsButtonCategory ? className : `solid ${className}`;
|
||||
// Check if the className contains any of the categories: solid, outlined, or ghost
|
||||
const containsButtonCategory = /(solid|outline|ghost)/.test(className);
|
||||
// If no category is present, default to 'solid'
|
||||
const categoryClassName = containsButtonCategory ? className : `solid ${className}`;
|
||||
|
||||
// Check if the className contains any of the color options: green, grey, red, or yellow
|
||||
const containsColor = /(green|grey|red|yellow)/.test(categoryClassName);
|
||||
// If no color is present, default to 'green'
|
||||
const finalClassName = containsColor ? categoryClassName : `green ${categoryClassName}`;
|
||||
// Check if the className contains any of the color options: green, grey, red, or yellow
|
||||
const containsColor = /(green|grey|red|yellow)/.test(categoryClassName);
|
||||
// If no color is present, default to 'green'
|
||||
const finalClassName = containsColor ? categoryClassName : `green ${categoryClassName}`;
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={btnRef}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
className={clsx("button", finalClassName)}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
{childrenArray}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
)
|
||||
return (
|
||||
<button
|
||||
ref={btnRef}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
className={clsx("button", finalClassName)}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
{childrenArray}
|
||||
</button>
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button };
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
z-index: 1000; // TODO: put this in theme.less
|
||||
display: flex;
|
||||
width: 125px;
|
||||
padding: 2px;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
gap: 1px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
background: #212121;
|
||||
box-shadow: 0px 8px 24px 0px rgba(0, 0, 0, 0.3);
|
||||
|
||||
.dropdown-item {
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
letter-spacing: -0.12px;
|
||||
width: 100%;
|
||||
border-radius: 2px;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { fn } from "@storybook/test";
|
||||
import { useRef, useState } from "react";
|
||||
import { Dropdown } from "./dropdown";
|
||||
|
||||
const meta = {
|
||||
title: "Elements/Dropdown",
|
||||
component: Dropdown,
|
||||
args: {
|
||||
items: [],
|
||||
anchorRef: undefined,
|
||||
boundaryRef: undefined,
|
||||
className: "",
|
||||
setVisibility: fn(),
|
||||
},
|
||||
argTypes: {
|
||||
items: {
|
||||
description: "Items of dropdown",
|
||||
},
|
||||
anchorRef: {
|
||||
description: "Element to attach the dropdown",
|
||||
},
|
||||
setVisibility: {
|
||||
description: "Visibility event handler",
|
||||
},
|
||||
boundaryRef: {
|
||||
description: "Component that defines the boundaries of the dropdown",
|
||||
},
|
||||
className: {
|
||||
description: "Custom className",
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Dropdown>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const DefaultRender: Story = {
|
||||
render: (args) => {
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
const boundaryRef = useRef<HTMLDivElement>(null);
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
|
||||
|
||||
const handleAnchorClick = () => {
|
||||
setIsDropdownVisible((prev) => !prev);
|
||||
};
|
||||
|
||||
const mapItemsWithClick = (items: any[]) => {
|
||||
return items.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
// Call the original onClick if it exists
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
}
|
||||
// Close the dropdown after an item is clicked
|
||||
setIsDropdownVisible(false);
|
||||
},
|
||||
// Recursively update subItems' onClick handlers
|
||||
subItems: item.subItems ? mapItemsWithClick(item.subItems) : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
// Modify args to include updated items with the new onClick behavior
|
||||
const modifiedArgs = {
|
||||
...args,
|
||||
items: mapItemsWithClick(args.items),
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={boundaryRef}
|
||||
className="boundary"
|
||||
style={{ padding: "20px", height: "300px", border: "2px solid black" }}
|
||||
>
|
||||
<div style={{ height: "400px" }}>
|
||||
<div
|
||||
ref={anchorRef}
|
||||
style={{
|
||||
backgroundColor: "lightblue",
|
||||
padding: "10px",
|
||||
display: "inline-block",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={handleAnchorClick}
|
||||
>
|
||||
Anchor Element
|
||||
</div>
|
||||
</div>
|
||||
{isDropdownVisible && (
|
||||
<Dropdown
|
||||
{...modifiedArgs}
|
||||
setVisibility={(visible) => setIsDropdownVisible(visible)}
|
||||
anchorRef={anchorRef}
|
||||
boundaryRef={boundaryRef}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
args: {
|
||||
items: [
|
||||
{ label: "Option 1", onClick: (e) => console.log("Clicked Option 1") },
|
||||
{
|
||||
label: "Option 2",
|
||||
onClick: (e) => console.log("Clicked Option 2"),
|
||||
subItems: [
|
||||
{ label: "Option 2 -> 1", onClick: (e) => console.log("Clicked Option 2 -> 1") },
|
||||
{ label: "Option 2 -> 2", onClick: (e) => console.log("Clicked Option 2 -> 2") },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Option 3",
|
||||
onClick: (e) => console.log("Clicked Option 3"),
|
||||
subItems: [
|
||||
{ label: "Option 3 -> 1", onClick: (e) => console.log("Clicked Option 3 -> 1") },
|
||||
{ label: "Option 3 -> 2", onClick: (e) => console.log("Clicked Option 3 -> 2") },
|
||||
{
|
||||
label: "Option 3 -> 3",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3"),
|
||||
subItems: [
|
||||
{ label: "Option 3 -> 3 -> 1", onClick: (e) => console.log("Clicked Option 3 -> 3 -> 1") },
|
||||
{ label: "Option 3 -> 3 -> 2", onClick: (e) => console.log("Clicked Option 3 -> 3 -> 2") },
|
||||
{ label: "Option 3 -> 3 -> 3", onClick: (e) => console.log("Clicked Option 3 -> 3 -> 3") },
|
||||
{
|
||||
label: "Option 3 -> 3 -> 4",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3 -> 4"),
|
||||
subItems: [
|
||||
{
|
||||
label: "Option 3 -> 3 -> 4 -> 1",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3 -> 4 -> 1"),
|
||||
},
|
||||
{
|
||||
label: "Option 3 -> 3 -> 4 -> 2",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3 -> 4 -> 2"),
|
||||
},
|
||||
{
|
||||
label: "Option 3 -> 3 -> 4 -> 3",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3 -> 4 -> 3"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Option 4",
|
||||
onClick: (e) => console.log("Clicked Option 4"),
|
||||
subItems: [
|
||||
{ label: "Option 4 -> 1", onClick: (e) => console.log("Clicked Option 4 -> 1") },
|
||||
{ label: "Option 4 -> 2", onClick: (e) => console.log("Clicked Option 4 -> 2") },
|
||||
{ label: "Option 4 -> 3", onClick: (e) => console.log("Clicked Option 4 -> 3") },
|
||||
{ label: "Option 4 -> 4", onClick: (e) => console.log("Clicked Option 4 -> 4") },
|
||||
{ label: "Option 4 -> 5", onClick: (e) => console.log("Clicked Option 4 -> 5") },
|
||||
{ label: "Option 4 -> 6", onClick: (e) => console.log("Clicked Option 4 -> 6") },
|
||||
{ label: "Option 4 -> 7", onClick: (e) => console.log("Clicked Option 4 -> 7") },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomRender: Story = {
|
||||
render: (args) => {
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
const boundaryRef = useRef<HTMLDivElement>(null);
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
|
||||
|
||||
const handleAnchorClick = () => {
|
||||
setIsDropdownVisible((prev) => !prev);
|
||||
};
|
||||
|
||||
const mapItemsWithClick = (items: any[]) => {
|
||||
return items.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
// Call the original onClick if it exists
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
}
|
||||
// Close the dropdown after an item is clicked
|
||||
setIsDropdownVisible(false);
|
||||
},
|
||||
// Recursively update subItems' onClick handlers
|
||||
subItems: item.subItems ? mapItemsWithClick(item.subItems) : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
// Custom render function for menu items
|
||||
const renderMenuItem = (item: any, props: any) => (
|
||||
<div {...props}>
|
||||
<strong>{item.label}</strong>
|
||||
{item.subItems && <span style={{ marginLeft: "10px", color: "#888" }}>â–¶</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
// Custom render function for the entire menu
|
||||
const renderMenu = (subMenu: JSX.Element) => <div>{subMenu}</div>;
|
||||
|
||||
// Modify args to include updated items with the new onClick behavior
|
||||
const modifiedArgs = {
|
||||
...args,
|
||||
items: mapItemsWithClick(args.items),
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={boundaryRef}
|
||||
className="boundary"
|
||||
style={{ padding: "20px", height: "300px", border: "2px solid black" }}
|
||||
>
|
||||
<div style={{ height: "400px" }}>
|
||||
<div
|
||||
ref={anchorRef}
|
||||
style={{
|
||||
backgroundColor: "lightblue",
|
||||
padding: "10px",
|
||||
display: "inline-block",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={handleAnchorClick}
|
||||
>
|
||||
Anchor Element
|
||||
</div>
|
||||
</div>
|
||||
{isDropdownVisible && (
|
||||
<Dropdown
|
||||
{...modifiedArgs}
|
||||
setVisibility={(visible) => setIsDropdownVisible(visible)}
|
||||
anchorRef={anchorRef}
|
||||
boundaryRef={boundaryRef}
|
||||
renderMenu={renderMenu}
|
||||
renderMenuItem={renderMenuItem}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
args: {
|
||||
items: [
|
||||
{ label: "Option 1", onClick: (e) => console.log("Clicked Option 1") },
|
||||
{
|
||||
label: "Option 2",
|
||||
onClick: (e) => console.log("Clicked Option 2"),
|
||||
subItems: [
|
||||
{ label: "Option 2 -> 1", onClick: (e) => console.log("Clicked Option 2 -> 1") },
|
||||
{ label: "Option 2 -> 2", onClick: (e) => console.log("Clicked Option 2 -> 2") },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Option 3",
|
||||
onClick: (e) => console.log("Clicked Option 3"),
|
||||
subItems: [
|
||||
{ label: "Option 3 -> 1", onClick: (e) => console.log("Clicked Option 3 -> 1") },
|
||||
{ label: "Option 3 -> 2", onClick: (e) => console.log("Clicked Option 3 -> 2") },
|
||||
{
|
||||
label: "Option 3 -> 3",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3"),
|
||||
subItems: [
|
||||
{ label: "Option 3 -> 3 -> 1", onClick: (e) => console.log("Clicked Option 3 -> 3 -> 1") },
|
||||
{ label: "Option 3 -> 3 -> 2", onClick: (e) => console.log("Clicked Option 3 -> 3 -> 2") },
|
||||
{ label: "Option 3 -> 3 -> 3", onClick: (e) => console.log("Clicked Option 3 -> 3 -> 3") },
|
||||
{
|
||||
label: "Option 3 -> 3 -> 4",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3 -> 4"),
|
||||
subItems: [
|
||||
{
|
||||
label: "Option 3 -> 3 -> 4 -> 1",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3 -> 4 -> 1"),
|
||||
},
|
||||
{
|
||||
label: "Option 3 -> 3 -> 4 -> 2",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3 -> 4 -> 2"),
|
||||
},
|
||||
{
|
||||
label: "Option 3 -> 3 -> 4 -> 3",
|
||||
onClick: (e) => console.log("Clicked Option 3 -> 3 -> 4 -> 3"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Option 4",
|
||||
onClick: (e) => console.log("Clicked Option 4"),
|
||||
subItems: [
|
||||
{ label: "Option 4 -> 1", onClick: (e) => console.log("Clicked Option 4 -> 1") },
|
||||
{ label: "Option 4 -> 2", onClick: (e) => console.log("Clicked Option 4 -> 2") },
|
||||
{ label: "Option 4 -> 3", onClick: (e) => console.log("Clicked Option 4 -> 3") },
|
||||
{ label: "Option 4 -> 4", onClick: (e) => console.log("Clicked Option 4 -> 4") },
|
||||
{ label: "Option 4 -> 5", onClick: (e) => console.log("Clicked Option 4 -> 5") },
|
||||
{ label: "Option 4 -> 6", onClick: (e) => console.log("Clicked Option 4 -> 6") },
|
||||
{ label: "Option 4 -> 7", onClick: (e) => console.log("Clicked Option 4 -> 7") },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,351 @@
|
||||
import { useHeight } from "@/app/hook/useHeight";
|
||||
import { useWidth } from "@/app/hook/useWidth";
|
||||
import clsx from "clsx";
|
||||
import React, { memo, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./dropdown.less";
|
||||
|
||||
type DropdownItem = {
|
||||
label: string;
|
||||
subItems?: DropdownItem[];
|
||||
onClick?: (e) => void;
|
||||
};
|
||||
|
||||
const SubMenu = memo(
|
||||
({
|
||||
subItems,
|
||||
parentKey,
|
||||
subMenuPosition,
|
||||
visibleSubMenus,
|
||||
hoveredItems,
|
||||
subMenuRefs,
|
||||
handleMouseEnterItem,
|
||||
handleOnClick,
|
||||
renderMenu,
|
||||
renderMenuItem,
|
||||
}: {
|
||||
subItems: DropdownItem[];
|
||||
parentKey: string;
|
||||
subMenuPosition: {
|
||||
[key: string]: { top: number; left: number; label: string };
|
||||
};
|
||||
visibleSubMenus: { [key: string]: any };
|
||||
hoveredItems: string[];
|
||||
subMenuRefs: React.MutableRefObject<{ [key: string]: React.RefObject<HTMLDivElement> }>;
|
||||
handleMouseEnterItem: (
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
parentKey: string | null,
|
||||
index: number,
|
||||
item: DropdownItem
|
||||
) => void;
|
||||
handleOnClick: (e: React.MouseEvent<HTMLDivElement>, item: DropdownItem) => void;
|
||||
renderMenu?: (subMenu: JSX.Element, props: any) => JSX.Element;
|
||||
renderMenuItem?: (item: DropdownItem, props: any) => JSX.Element;
|
||||
}) => {
|
||||
subItems.forEach((_, idx) => {
|
||||
const newKey = `${parentKey}-${idx}`;
|
||||
if (!subMenuRefs.current[newKey]) {
|
||||
subMenuRefs.current[newKey] = React.createRef<HTMLDivElement>();
|
||||
}
|
||||
});
|
||||
|
||||
const position = subMenuPosition[parentKey];
|
||||
const isPositioned = position && position.top !== undefined && position.left !== undefined;
|
||||
|
||||
const subMenu = (
|
||||
<div
|
||||
className="dropdown sub-dropdown"
|
||||
ref={subMenuRefs.current[parentKey]}
|
||||
style={{
|
||||
top: position?.top || 0,
|
||||
left: position?.left || 0,
|
||||
position: "absolute",
|
||||
zIndex: 1000,
|
||||
visibility: visibleSubMenus[parentKey]?.visible && isPositioned ? "visible" : "hidden",
|
||||
}}
|
||||
>
|
||||
{subItems.map((item, idx) => {
|
||||
const newKey = `${parentKey}-${idx}`;
|
||||
const isActive = hoveredItems.includes(newKey);
|
||||
|
||||
const menuItemProps = {
|
||||
className: clsx("dropdown-item", { active: isActive }),
|
||||
onMouseEnter: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) =>
|
||||
handleMouseEnterItem(event, parentKey, idx, item),
|
||||
onClick: (e: React.MouseEvent<HTMLDivElement>) => handleOnClick(e, item),
|
||||
};
|
||||
|
||||
const renderedItem = renderMenuItem ? (
|
||||
renderMenuItem(item, menuItemProps) // Remove portal here
|
||||
) : (
|
||||
<div key={newKey} {...menuItemProps}>
|
||||
{item.label}
|
||||
{item.subItems && <span className="arrow">â–¶</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment key={newKey}>
|
||||
{renderedItem}
|
||||
{visibleSubMenus[newKey]?.visible && item.subItems && (
|
||||
<SubMenu
|
||||
subItems={item.subItems}
|
||||
parentKey={newKey}
|
||||
subMenuPosition={subMenuPosition}
|
||||
visibleSubMenus={visibleSubMenus}
|
||||
hoveredItems={hoveredItems}
|
||||
handleMouseEnterItem={handleMouseEnterItem}
|
||||
handleOnClick={handleOnClick}
|
||||
subMenuRefs={subMenuRefs}
|
||||
renderMenu={renderMenu}
|
||||
renderMenuItem={renderMenuItem}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
return ReactDOM.createPortal(renderMenu ? renderMenu(subMenu, { parentKey }) : subMenu, document.body);
|
||||
}
|
||||
);
|
||||
|
||||
const Dropdown = memo(
|
||||
({
|
||||
items,
|
||||
anchorRef,
|
||||
boundaryRef,
|
||||
className,
|
||||
setVisibility,
|
||||
renderMenu,
|
||||
renderMenuItem,
|
||||
}: {
|
||||
items: DropdownItem[];
|
||||
anchorRef: React.RefObject<HTMLElement>;
|
||||
boundaryRef?: React.RefObject<HTMLElement>;
|
||||
className?: string;
|
||||
setVisibility: (_: boolean) => void;
|
||||
renderMenu?: (subMenu: JSX.Element, props: any) => JSX.Element;
|
||||
renderMenuItem?: (item: DropdownItem, props: any) => JSX.Element;
|
||||
}) => {
|
||||
const [visibleSubMenus, setVisibleSubMenus] = useState<{ [key: string]: any }>({});
|
||||
const [hoveredItems, setHoveredItems] = useState<string[]>([]);
|
||||
const [subMenuPosition, setSubMenuPosition] = useState<{
|
||||
[key: string]: { top: number; left: number; label: string };
|
||||
}>({});
|
||||
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const subMenuRefs = useRef<{ [key: string]: React.RefObject<HTMLDivElement> }>({});
|
||||
|
||||
const effectiveBoundaryRef: React.RefObject<HTMLElement> = boundaryRef ?? { current: document.documentElement };
|
||||
const width = useWidth(effectiveBoundaryRef);
|
||||
const height = useHeight(effectiveBoundaryRef);
|
||||
|
||||
items.forEach((_, idx) => {
|
||||
const key = `${idx}`;
|
||||
if (!subMenuRefs.current[key]) {
|
||||
subMenuRefs.current[key] = React.createRef<HTMLDivElement>();
|
||||
}
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (anchorRef.current && dropdownRef.current) {
|
||||
const anchorRect = anchorRef.current.getBoundingClientRect();
|
||||
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
|
||||
|
||||
let top = anchorRect.bottom + scrollTop;
|
||||
let left = anchorRect.left + scrollLeft;
|
||||
|
||||
const boundaryRect = effectiveBoundaryRef.current?.getBoundingClientRect() || {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: window.innerHeight,
|
||||
right: window.innerWidth,
|
||||
};
|
||||
|
||||
if (left + dropdownRef.current.offsetWidth > boundaryRect.right + scrollLeft) {
|
||||
left = boundaryRect.right + scrollLeft - dropdownRef.current.offsetWidth;
|
||||
}
|
||||
|
||||
if (top + dropdownRef.current.offsetHeight > boundaryRect.bottom + scrollTop) {
|
||||
top = boundaryRect.bottom + scrollTop - dropdownRef.current.offsetHeight;
|
||||
}
|
||||
|
||||
setPosition({ top, left });
|
||||
}
|
||||
}, [width, height]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const isClickInsideDropdown = dropdownRef.current && dropdownRef.current.contains(event.target as Node);
|
||||
const isClickInsideAnchor = anchorRef.current && anchorRef.current.contains(event.target as Node);
|
||||
|
||||
const isClickInsideSubMenus = Object.keys(subMenuRefs.current).some(
|
||||
(key) =>
|
||||
subMenuRefs.current[key]?.current &&
|
||||
subMenuRefs.current[key]?.current.contains(event.target as Node)
|
||||
);
|
||||
|
||||
if (!isClickInsideDropdown && !isClickInsideAnchor && !isClickInsideSubMenus) {
|
||||
setVisibility(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [dropdownRef, anchorRef, subMenuRefs]);
|
||||
|
||||
// Position submenus based on available space and scroll position
|
||||
const handleSubMenuPosition = (
|
||||
key: string,
|
||||
itemRect: DOMRect,
|
||||
parentRef: React.RefObject<HTMLDivElement>,
|
||||
label: string
|
||||
) => {
|
||||
setTimeout(() => {
|
||||
const subMenuRef = subMenuRefs.current[key]?.current;
|
||||
if (!subMenuRef) return;
|
||||
|
||||
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
|
||||
|
||||
const boundaryRect = effectiveBoundaryRef.current?.getBoundingClientRect() || {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: window.innerHeight,
|
||||
right: window.innerWidth,
|
||||
};
|
||||
|
||||
const submenuWidth = subMenuRef.offsetWidth;
|
||||
const submenuHeight = subMenuRef.offsetHeight;
|
||||
|
||||
let left = itemRect.right + scrollLeft - 2; // Adjust for horizontal scroll
|
||||
let top = itemRect.top + scrollTop; // Adjust for vertical scroll
|
||||
|
||||
// Adjust to the left if overflowing the right boundary
|
||||
if (left + submenuWidth > window.innerWidth + scrollLeft) {
|
||||
left = itemRect.left + scrollLeft - submenuWidth;
|
||||
}
|
||||
|
||||
// Adjust if the submenu overflows the bottom boundary
|
||||
if (top + submenuHeight > window.innerHeight + scrollTop) {
|
||||
top = window.innerHeight + scrollTop - submenuHeight - 10;
|
||||
}
|
||||
|
||||
setSubMenuPosition((prev) => ({
|
||||
...prev,
|
||||
[key]: { top, left, label },
|
||||
}));
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const handleMouseEnterItem = (
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
parentKey: string | null,
|
||||
index: number,
|
||||
item: DropdownItem
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
|
||||
const key = parentKey ? `${parentKey}-${index}` : `${index}`;
|
||||
|
||||
setVisibleSubMenus((prev) => {
|
||||
const updatedState = { ...prev };
|
||||
updatedState[key] = { visible: true, label: item.label };
|
||||
|
||||
const ancestors = key.split("-").reduce((acc, part, idx) => {
|
||||
if (idx === 0) return [part];
|
||||
return [...acc, `${acc[idx - 1]}-${part}`];
|
||||
}, [] as string[]);
|
||||
|
||||
ancestors.forEach((ancestorKey) => {
|
||||
if (updatedState[ancestorKey]) {
|
||||
updatedState[ancestorKey].visible = true;
|
||||
}
|
||||
});
|
||||
|
||||
for (const pkey in updatedState) {
|
||||
if (!ancestors.includes(pkey) && pkey !== key) {
|
||||
updatedState[pkey].visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
return updatedState;
|
||||
});
|
||||
|
||||
const newHoveredItems = key.split("-").reduce((acc, part, idx) => {
|
||||
if (idx === 0) return [part];
|
||||
return [...acc, `${acc[idx - 1]}-${part}`];
|
||||
}, [] as string[]);
|
||||
|
||||
setHoveredItems(newHoveredItems);
|
||||
|
||||
const itemRect = event.currentTarget.getBoundingClientRect();
|
||||
handleSubMenuPosition(key, itemRect, dropdownRef, item.label);
|
||||
};
|
||||
|
||||
const handleOnClick = (e: React.MouseEvent<HTMLDivElement>, item: DropdownItem) => {
|
||||
e.stopPropagation();
|
||||
item.onClick && item.onClick(e);
|
||||
};
|
||||
|
||||
const dropdownMenu = (
|
||||
<div
|
||||
className={clsx("dropdown", className)}
|
||||
ref={dropdownRef}
|
||||
style={{ top: position.top, left: position.left }}
|
||||
>
|
||||
{items.map((item, index) => {
|
||||
const key = `${index}`;
|
||||
const isActive = hoveredItems.includes(key);
|
||||
|
||||
const menuItemProps = {
|
||||
className: clsx("dropdown-item", { active: isActive }),
|
||||
onMouseEnter: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) =>
|
||||
handleMouseEnterItem(event, null, index, item),
|
||||
onClick: (e: React.MouseEvent<HTMLDivElement>) => handleOnClick(e, item),
|
||||
};
|
||||
|
||||
const renderedItem = renderMenuItem ? (
|
||||
renderMenuItem(item, menuItemProps) // No portal here
|
||||
) : (
|
||||
<div key={key} {...menuItemProps}>
|
||||
{item.label}
|
||||
{item.subItems && <span className="arrow">â–¶</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{renderedItem}
|
||||
{visibleSubMenus[key]?.visible && item.subItems && (
|
||||
<SubMenu
|
||||
subItems={item.subItems}
|
||||
parentKey={key}
|
||||
subMenuPosition={subMenuPosition}
|
||||
visibleSubMenus={visibleSubMenus}
|
||||
hoveredItems={hoveredItems}
|
||||
handleMouseEnterItem={handleMouseEnterItem}
|
||||
handleOnClick={handleOnClick}
|
||||
subMenuRefs={subMenuRefs}
|
||||
renderMenu={renderMenu}
|
||||
renderMenuItem={renderMenuItem}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
renderMenu ? renderMenu(dropdownMenu, { parentKey: null }) : dropdownMenu,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { Dropdown };
|
||||
@@ -342,18 +342,24 @@ const ChatWindow = memo(
|
||||
|
||||
const handleScrollbarInitialized = (instance: OverlayScrollbars) => {
|
||||
const { viewport } = instance.elements();
|
||||
viewport.removeAttribute("tabindex");
|
||||
viewport.scrollTo({
|
||||
behavior: "auto",
|
||||
top: chatWindowRef.current?.scrollHeight || 0,
|
||||
});
|
||||
};
|
||||
|
||||
const handleScrollbarUpdated = (instance: OverlayScrollbars) => {
|
||||
const { viewport } = instance.elements();
|
||||
viewport.removeAttribute("tabindex");
|
||||
};
|
||||
|
||||
return (
|
||||
<OverlayScrollbarsComponent
|
||||
ref={osRef}
|
||||
className="scrollable"
|
||||
options={{ scrollbars: { autoHide: "leave" } }}
|
||||
events={{ initialized: handleScrollbarInitialized }}
|
||||
events={{ initialized: handleScrollbarInitialized, updated: handleScrollbarUpdated }}
|
||||
>
|
||||
<div ref={chatWindowRef} className="chat-window" style={msgWidths}>
|
||||
<div className="filler"></div>
|
||||
@@ -491,22 +497,6 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
||||
setSelectedBlockIdx(null);
|
||||
}, [messages, value]);
|
||||
|
||||
const handleContainerClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
inputRef.current?.focus();
|
||||
|
||||
const target = event.target as HTMLElement;
|
||||
if (
|
||||
target.closest(".copy-button") ||
|
||||
target.closest(".fa-square-terminal") ||
|
||||
target.closest(".waveai-input")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pre = target.closest("pre");
|
||||
updatePreTagOutline(pre);
|
||||
};
|
||||
|
||||
const updateScrollTop = () => {
|
||||
const pres = chatWindowRef.current?.querySelectorAll("pre");
|
||||
if (!pres || selectedBlockIdx === null) return;
|
||||
@@ -610,7 +600,7 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
||||
}, [locked, handleEnterKeyPressed]);
|
||||
|
||||
return (
|
||||
<div ref={waveaiRef} className="waveai" onClick={handleContainerClick}>
|
||||
<div ref={waveaiRef} className="waveai">
|
||||
<ChatWindow ref={osRef} chatWindowRef={chatWindowRef} messages={messages} msgWidths={msgWidths} />
|
||||
<div className="waveai-controls">
|
||||
<div className="waveai-input-wrapper">
|
||||
|
||||
@@ -302,7 +302,7 @@ export class LayoutModel {
|
||||
* Perform an action against the layout tree state.
|
||||
* @param action The action to perform.
|
||||
*/
|
||||
treeReducer(action: LayoutTreeAction) {
|
||||
treeReducer(action: LayoutTreeAction, setState = true) {
|
||||
switch (action.type) {
|
||||
case LayoutTreeActionType.ComputeMove:
|
||||
this.setter(
|
||||
@@ -369,7 +369,7 @@ export class LayoutModel {
|
||||
this.magnifiedNodeId = this.treeState.magnifiedNodeId;
|
||||
}
|
||||
this.updateTree();
|
||||
this.setTreeStateAtom(true);
|
||||
if (setState) this.setTreeStateAtom(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ export class LayoutModel {
|
||||
* Callback that is invoked when the upstream tree state has been updated. This ensures the model is updated if the atom is not fully loaded when the model is first instantiated.
|
||||
* @param force Whether to force the local tree state to update, regardless of whether the state is already up to date.
|
||||
*/
|
||||
onTreeStateAtomUpdated = debounce(50, async (force = false) => {
|
||||
async onTreeStateAtomUpdated(force = false) {
|
||||
const treeState = this.getter(this.treeStateAtom);
|
||||
// Only update the local tree state if it is different from the one in the upstream atom. This function is called even when the update was initiated by the LayoutModel, so we need to filter out false positives or we'll enter an infinite loop.
|
||||
if (
|
||||
@@ -403,7 +403,7 @@ export class LayoutModel {
|
||||
magnified: action.magnified,
|
||||
focused: action.focused,
|
||||
};
|
||||
this.treeReducer(insertNodeAction);
|
||||
this.treeReducer(insertNodeAction, false);
|
||||
break;
|
||||
}
|
||||
case LayoutTreeActionType.DeleteNode: {
|
||||
@@ -434,13 +434,16 @@ export class LayoutModel {
|
||||
magnified: action.magnified,
|
||||
focused: action.focused,
|
||||
};
|
||||
this.treeReducer(insertAction);
|
||||
this.treeReducer(insertAction, false);
|
||||
break;
|
||||
}
|
||||
case LayoutTreeActionType.ClearTree: {
|
||||
this.treeReducer({
|
||||
type: LayoutTreeActionType.ClearTree,
|
||||
} as LayoutTreeClearTreeAction);
|
||||
this.treeReducer(
|
||||
{
|
||||
type: LayoutTreeActionType.ClearTree,
|
||||
} as LayoutTreeClearTreeAction,
|
||||
false
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -448,12 +451,13 @@ export class LayoutModel {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.setTreeStateAtom(true);
|
||||
} else {
|
||||
this.updateTree();
|
||||
this.setTreeStateAtom(force);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upstream tree state atom to the value of the local tree state.
|
||||
|
||||
@@ -25,7 +25,7 @@ export function getLayoutModelForTab(tabAtom: Atom<Tab>): LayoutModel {
|
||||
}
|
||||
const layoutTreeStateAtom = withLayoutTreeStateAtomFromTab(tabAtom);
|
||||
const layoutModel = new LayoutModel(layoutTreeStateAtom, globalStore.get, globalStore.set);
|
||||
globalStore.sub(layoutTreeStateAtom, () => fireAndForget(() => layoutModel.onTreeStateAtomUpdated()));
|
||||
globalStore.sub(layoutTreeStateAtom, () => fireAndForget(async () => layoutModel.onTreeStateAtomUpdated()));
|
||||
layoutModelMap.set(tabId, layoutModel);
|
||||
return layoutModel;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export function useTileLayout(tabAtom: Atom<Tab>, tileContent: TileLayoutContent
|
||||
useResizeObserver(layoutModel?.displayContainerRef, layoutModel?.onContainerResize);
|
||||
|
||||
// Once the TileLayout is mounted, re-run the state update to get all the nodes to flow in the layout.
|
||||
useLayoutEffect(() => fireAndForget(() => layoutModel.onTreeStateAtomUpdated(true)), []);
|
||||
useEffect(() => fireAndForget(async () => layoutModel.onTreeStateAtomUpdated(true)), []);
|
||||
|
||||
useEffect(() => layoutModel.registerTileLayout(tileContent), [tileContent]);
|
||||
return layoutModel;
|
||||
|
||||
@@ -54,7 +54,7 @@ export function focusedBlockId(): string {
|
||||
}
|
||||
}
|
||||
const sel = document.getSelection();
|
||||
if (sel && sel.anchorNode) {
|
||||
if (sel && sel.anchorNode && sel.rangeCount > 0 && !sel.isCollapsed) {
|
||||
let anchor = sel.anchorNode;
|
||||
if (anchor instanceof Text) {
|
||||
anchor = anchor.parentElement;
|
||||
|
||||
+9
-9
@@ -7,7 +7,7 @@
|
||||
"productName": "Wave",
|
||||
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.8.0-beta.6",
|
||||
"version": "0.8.0-beta.7",
|
||||
"homepage": "https://waveterm.dev",
|
||||
"build": {
|
||||
"appId": "dev.commandline.waveterm"
|
||||
@@ -30,13 +30,13 @@
|
||||
"@chromatic-com/storybook": "^2.0.2",
|
||||
"@eslint/js": "^9.10.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@storybook/addon-essentials": "^8.3.0",
|
||||
"@storybook/addon-interactions": "^8.3.0",
|
||||
"@storybook/addon-links": "^8.3.0",
|
||||
"@storybook/blocks": "^8.3.0",
|
||||
"@storybook/react": "^8.3.0",
|
||||
"@storybook/react-vite": "^8.3.0",
|
||||
"@storybook/test": "^8.3.0",
|
||||
"@storybook/addon-essentials": "^8.3.2",
|
||||
"@storybook/addon-interactions": "^8.3.2",
|
||||
"@storybook/addon-links": "^8.3.2",
|
||||
"@storybook/blocks": "^8.3.2",
|
||||
"@storybook/react": "^8.3.2",
|
||||
"@storybook/react-vite": "^8.3.2",
|
||||
"@storybook/test": "^8.3.2",
|
||||
"@types/css-tree": "^2",
|
||||
"@types/debug": "^4",
|
||||
"@types/electron": "^1.6.10",
|
||||
@@ -65,7 +65,7 @@
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"rollup-plugin-flow": "^1.1.1",
|
||||
"semver": "^7.6.3",
|
||||
"storybook": "^8.3.0",
|
||||
"storybook": "^8.3.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.19.1",
|
||||
|
||||
Reference in New Issue
Block a user