chore: docs for release v1.6.0

This commit is contained in:
mathuo 2023-02-16 21:54:35 +07:00
parent 4bd6eba53a
commit 9e8b0fddd4
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
17 changed files with 964 additions and 101 deletions

View File

@ -0,0 +1,33 @@
---
slug: dockview-1.6.0-release
title: Dockview 1.6.0
tags: [release]
---
import Link from '@docusaurus/Link';
# Release Notes
Please reference to docs @ [dockview.dev](https://dockview.dev).
If you feel anything is missing or unclear please let me know.
## 🚀 Features
- magnetic dnd controls [#177](https://github.com/mathuo/dockview/pull/177)
- group dnd [#171](https://github.com/mathuo/dockview/pull/171)
- full width tabs [#171](https://github.com/mathuo/dockview/pull/177)
- addPanel improvements
- update parameters via panel.api.updateParameters
- allow dnd on empty groups [#168](https://github.com/mathuo/dockview/pull/168)
## 🛠 Miscs
- Update dependencies including the dev dependencies for dockview and all dependencies for the docs website.
[#180](https://github.com/mathuo/dockview/pull/180)
- A variety of internal changes including file name changes
- Improve internal dnd control logic to handle a wider variety of cases
- Various doc enhancements @ [dockview.dev](https://dockview.dev)
## 🔥 Breaking changes
- addEmptyGroup renamed to addGroup

View File

@ -13,7 +13,13 @@ import { ContextMenuDockview } from '@site/src/components/dockview/contextMenu';
import { NestedDockview } from '@site/src/components/dockview/nested'; import { NestedDockview } from '@site/src/components/dockview/nested';
import { CustomHeadersDockview } from '@site/src/components/dockview/customHeaders'; import { CustomHeadersDockview } from '@site/src/components/dockview/customHeaders';
import { ResizeDockview } from '@site/src/components/dockview/resize'; import { ResizeDockview } from '@site/src/components/dockview/resize';
import { DockviewGroupControl } from '@site/src/components/dockview/groupControl';
import {
DockviewNative,
DockviewNative2,
} from '@site/src/components/dockview/native';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
# Dockview # Dockview
@ -44,7 +50,7 @@ import { ReactDockview } from 'dockview';
``` ```
| Property | Type | Optional | Default | Description | | Property | Type | Optional | Default | Description |
| ------------------- | ------------------------------------ | -------- | ------- | ------------------------------------------------------------ | | --------------------- | ------------------------------------ | -------- | --------- | ------------------------------------------------------------ |
| onReady | (event: SplitviewReadyEvent) => void | No | | | | onReady | (event: SplitviewReadyEvent) => void | No | | |
| components | object | No | | | | components | object | No | | |
| tabComponents | object | Yes | | | | tabComponents | object | Yes | | |
@ -54,6 +60,10 @@ import { ReactDockview } from 'dockview';
| disableAutoResizing | boolean | Yes | false | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> | | disableAutoResizing | boolean | Yes | false | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
| onDidDrop | Event | Yes | false | | | onDidDrop | Event | Yes | false | |
| showDndOverlay | Event | Yes | false | | | showDndOverlay | Event | Yes | false | |
| defaultTabComponent | object | Yes | | |
| groupControlComponent | object | Yes | | |
| tabHeight | number | Yes | | |
| singleTabMode | 'fullwidth' \| 'default' | Yes | 'default' | |
## Dockview API ## Dockview API
@ -101,13 +111,13 @@ const onReady = (event: DockviewReadyEvent) => {
| | | | | | | |
| addPanel | `addPanel(options: AddPanelOptions): IDockviewPanel` | | | addPanel | `addPanel(options: AddPanelOptions): IDockviewPanel` | |
| getPanel | `(id: string) \| IDockviewPanel \| undefined` | | | getPanel | `(id: string) \| IDockviewPanel \| undefined` | |
| addEmptyGroup | `(options? AddGroupOptions): void` | | | addGroup | `(options? AddGroupOptions): void` | |
| closeAllGroups | `(): void` | | | closeAllGroups | `(): void` | |
| removeGroup | `(group: GroupPanel): void` | | | removeGroup | `(group: GroupPanel): void` | |
| getGroup | `(id: string): GroupPanel \| undefined` | | | getGroup | `(id: string): GroupPanel \| undefined` | |
| | | | | | | |
| getTabHeight | `(): number \| undefined` | | | getTabHeight | `(): number \| undefined` | |
| setTabHeight | `(hegiht: number \| undefined): void` | | | setTabHeight | `(height: number \| undefined): void` | |
| updateOptions | `(options:SplitviewComponentUpdateOptions): void` | | | updateOptions | `(options:SplitviewComponentUpdateOptions): void` | |
| focus | `(): void` | | | focus | `(): void` | |
| layout | `(width: number, height:number): void` | <Link to="../basics/#auto-resizing">Auto Resizing</Link> | | layout | `(width: number, height:number): void` | <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
@ -126,9 +136,9 @@ const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
``` ```
| Property | Type | Description | | Property | Type | Description |
| ---------------------- | ----------------------------------------------------------- | --------------- | | ---------------------- | ----------------------------------------------------------- | ---------------- |
| id | `string` | Panel id | | id | `string` | Panel id |
| isFocused | `boolean` | Is panel focsed | | isFocused | `boolean` | Is panel focused |
| isActive | `boolean` | Is panel active | | isActive | `boolean` | Is panel active |
| width | `number` | Panel width | | width | `number` | Panel width |
| height | `number` | Panel height | | height | `number` | Panel height |
@ -149,42 +159,115 @@ const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
| close | `(): void` | | | close | `(): void` | |
| setTitle | `(title: string): void` | | | setTitle | `(title: string): void` | |
## Essential Features
### Add Panel
Using the dockview API you can access the `addPanel` method which returns an instance of the created panel.
The minimum method signature is:
```ts
const panel = api.addPanel({
id: 'my_unique_panel_id',
component: 'my_component',
});
```
where `id` is the unique id of the panel and `component` is the implenentation which
will be used to render the panel. You will have registered this using the `components` prop of the `DockviewReactComponent` component.
You can optionally provide a `tabComponent` parameters to the `addPanel` method which will render the tab using a custom renderer.
You will have registered this using the `tabComponents` prop of the `DockviewReactComponent` component.
```ts
const panel = api.addPanel({
id: 'my_unique_panel_id',
component: 'my_component',
tabComponent: 'my_tab_component',
});
```
You can pass properties to the panel using the `params` key.
You can update these properties through the panels `api` object and its `updateParameters` method.
```ts
const panel = api.addPanel({
id: 'my_unique_panel_id',
component: 'my_component',
params: {
myCustomKey: 'my_custom_value',
},
});
panel.api.updateParameters({
myCustomKey: 'my_custom_value',
myOtherCustomKey: 'my_other_custom_key',
});
```
> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters
> you want the panel to receive.
Finally `addPanel` accepts a `position` object which tells dockview where to place the panel.
- This object optionally accepts either a `referencePanel` or `referenceGroup` which can be the associated id as a string
or the panel/group object reference.
- This object accepts a `direction` property which dictates where,
relative to the provided reference the new panel will be placed.
> If neither a `referencePanel` or `referenceGroup` then the provided `direction` will be treated as absolute.
> If no `direction` is provided the library will place the new panel in a pre-determined position.
```ts
const panel = api.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel_2',
component: 'default',
position: {
referencePanel: panel1,
direction: 'right',
},
});
```
## Advanced Features ## Advanced Features
### Resizing via API ### Resizing via API
Each Dockview is comprised of a number of groups, each of which have a number of panels. Each Dockview contains of a number of groups and each group has a number of panels.
Logically most people would want to resize a panel but practically this really translates to resizing the group to which the panel belongs. Logically a user may want to resize a panel, but this translates to resizing the group which contains that panel.
From the api you can access the panels group object (`props.group`) which exposes it's own api object (`props.groups.api`). You can set the size of a panel using `props.api.setSize(...)`.
This api is largly similar to the <Link to="./gridview/#gridview-panel-api">Gridview API</Link>. You can also set the size of the group associated with the panel using `props.api.group.api.setSize(...)` although this isn't recommended
due to the clunky syntax.
To resize an individual panel you could create a snippet similar to below.
```tsx ```tsx
const onResizePanel = () => { // it's mandatory to provide either a height or a width, providing both is optional
props.api.group.api.setSize({ props.api.setSize({
height: 100, height: 100,
}); width: 200,
}; });
// you could also resize the panels group, although not recommended it achieved the same result
props.api.group.api.setSize({
height: 100,
width: 200,
});
``` ```
```tsx You can see an example invoking both approaches below.
const onResizePanel = () => {
props.api.group.api.setSize({
width: 100,
});
};
```
Here is a working example of resizing panels via these API methods.
<ResizeDockview /> <ResizeDockview />
### Locked group ### Locked group
Locking a group will disable all drop events for this group ensuring a user can not add additional panels to the group. Locking a group will disable all drop events for this group ensuring no additional panels can be added to the group through drop events.
You can still add groups to a locked panel programatically using the API. You can still add groups to a locked panel programatically using the API though.
```tsx ```tsx
panel.group.locked = true; panel.group.locked = true;
@ -192,7 +275,7 @@ panel.group.locked = true;
### Group header ### Group header
You may wish to hide the header section of a group. This can achieved through setting the `hidden` variable on `panel.group.header`. You may wish to hide the header section of a group. This can achieved through the `hidden` variable on `panel.group.header`.
```tsx ```tsx
panel.group.header.hidden = true; panel.group.header.hidden = true;
@ -200,13 +283,11 @@ panel.group.header.hidden = true;
### Custom Tab Headers ### Custom Tab Headers
You can provide custom renderers for your tab headers. You can provide custom renderers for your tab headers for maximum customization.
A default implementation of `DockviewDefaultTab` is provided should you only wish to attach minor A default implementation of `DockviewDefaultTab` is provided should you only wish to attach minor
changes and events that do not alter the default behaviour, for example to add a custom context menu event changes and events that do not alter the default behaviour, for example to add a custom context menu event
handler. handler.
You are also free to define a custom renderer entirely from scratch and not make use of the `DockviewDefaultTab` component.
```tsx title="Attaching a custom context menu event handlers to a custom header" ```tsx title="Attaching a custom context menu event handlers to a custom header"
import { IDockviewPanelHeaderProps, DockviewDefaultTab } from 'dockview'; import { IDockviewPanelHeaderProps, DockviewDefaultTab } from 'dockview';
@ -219,7 +300,8 @@ const MyCustomheader = (props: IDockviewPanelHeaderProps) => {
}; };
``` ```
To use a custom renderer you can must register a collection of tab components You are also free to define a custom renderer entirely from scratch and not make use of the `DockviewDefaultTab` component.
To use a custom renderer you can must register a collection of tab components.
```tsx ```tsx
const tabComponents = { const tabComponents = {
@ -233,7 +315,7 @@ return <DockviewReact tabComponents={tabComponents} ... />;
api.addPanel({ api.addPanel({
id: 'panel_1', id: 'panel_1',
component: 'default', component: 'default',
tabComponent: 'myCustomHeader', // <-- tabComponent: 'myCustomHeader', // <-- your registered renderers
title: 'Panel 1', title: 'Panel 1',
}); });
``` ```
@ -246,26 +328,35 @@ You can also override the default tab renderer which will be used when no `tabCo
As a simple example the below attachs a custom event handler for the context menu on all tabs as a default tab renderer As a simple example the below attachs a custom event handler for the context menu on all tabs as a default tab renderer
The below example uses a custom tab renderer to reigster a popover when the user right clicked on a tab.
This still makes use of the `DockviewDefaultTab` since it's only a minor change.
<CustomHeadersDockview /> <CustomHeadersDockview />
### Rendering ### Panel Rendering
Although `DockviewReact` will only add those tabs that are visible to the DOM all associated React Components for each tab including those that By default `DockviewReact` only adds to the DOM those panels that are visible,
are not initially visible will be created. if a panel is not the active tab and not shown the contents of the hidden panel will be removed from the DOM.
This will mean that any hooks in those components will run and if you running expensive operations in the tabs you may end up doing a lot of initial
work for what are hidden tabs.
This is the default behaviour to ensure the greatest flexibility for the user but you can create a Higher-Order component wrapping your components that However the React Components associated with each panel are only created once and will always exist for as long as the panel exists, hidden or not.
will ensure the component is only created if the tab is visible as below:
```tsx > For example this means that any hooks in those components will run whether the panel is visible or not which may lead to excessive background work depending
import { PanelApi } from 'dockview'; > on the panels implementation.
This is the default behaviour to ensure the greatest flexibility for the user but through the panels `props.api` you can listen to the visiblity state of the panel
and write additional logic to optimize your application.
For example if you wanted to unmount the React Components when the panel is not visible you could create a Higher-Order-Component that listens to the panels
visiblity state and only renders the panel when visible.
```tsx title="Only rendering the React Component when the panel is visible, otherwise rendering a null React Component"
import { IDockviewPanelProps } from 'dockview';
import * as React from 'react'; import * as React from 'react';
function RenderWhenVisible< function RenderWhenVisible(
T extends { api: Pick<PanelApi, 'isVisible' | 'onDidVisibilityChange'> } component: React.FunctionComponent<IDockviewPanelProps>
>(component: React.FunctionComponent<T>) { ) {
const HigherOrderComponent = (props: T) => { const HigherOrderComponent = (props: IDockviewPanelProps) => {
const [visible, setVisible] = React.useState<boolean>( const [visible, setVisible] = React.useState<boolean>(
props.api.isVisible props.api.isVisible
); );
@ -291,10 +382,10 @@ function RenderWhenVisible<
``` ```
```tsx ```tsx
const component = RenderWhenVisible(MyComponent); const components = { default: RenderWhenVisible(MyComponent) };
``` ```
Through toggling the checkbox you can see that when you only render those panels which are visible the underling React component is destroyed when it becomes hidden and re-created when it becomes visible. Toggling the checkbox you can see that when you only render those panels which are visible the underling React component is destroyed when it becomes hidden and re-created when it becomes visible.
<Checkbox /> <Checkbox />
<div <div
@ -310,7 +401,40 @@ Through toggling the checkbox you can see that when you only render those panels
### Drag And Drop ### Drag And Drop
The component exposes some method to help determine whether external drag events should be interacted with or not. #### Built-in behaviours
Dockview supports a wide variety of built-in Drag and Drop possibilities.
Below are some examples of the operations you can perform.
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_tab.svg')} />
> Drag a tab onto another tab to place it inbetween existing tabs.
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_empty_space.svg')} />
> Drag a tab to the right of the last tab to place it after the existing tabs.
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_group.svg')} />
> Drag a group onto an existing group to merge the two groups.
<div style={{ display: 'flex', justifyContent: 'space-around' }}>
<img style={{ width: '40%' }} src={useBaseUrl('/img/drop_positions.svg')} />
<img
style={{ width: '40%' }}
src={useBaseUrl('/img/magnet_drop_positions.svg')}
/>
</div>
> Drag into the left/right/top/bottom target zone of a panel to create a new group in the selected direction.
> Drag into the center of a panel to add to that group.
> Drag to the edge of the dockview component to create a new group on the selected edge.
#### Extended behaviours
For interaction with the Drag events directly the component exposes some method to help determine whether external drag events should be interacted with or not.
```tsx ```tsx
/** /**
@ -375,3 +499,51 @@ const Component: React.FunctionComponent<IDockviewGroupControlProps> = () => {
return <DockviewReact {...props} groupControlComponent={Component} />; return <DockviewReact {...props} groupControlComponent={Component} />;
``` ```
As a simple example the below uses the `groupControlComponent` to render a small control that indicates whether the group
is active and which panel is active in that group.
```tsx
const GroupControlComponent = (props: IDockviewGroupControlProps) => {
const isGroupActive = props.isGroupActive;
const activePanel = props.activePanel;
return (
<div className="dockview-groupcontrol-demo">
<span
className="dockview-groupcontrol-demo-group-active"
style={{
background: isGroupActive ? 'green' : 'red',
}}
>
{isGroupActive ? 'Group Active' : 'Group Inactive'}
</span>
<span className="dockview-groupcontrol-demo-active-panel">{`activePanel: ${
activePanel?.id || 'null'
}`}</span>
</div>
);
};
```
<DockviewGroupControl />
### Full width tabs
`DockviewReactComponent` accepts the prop `singleTabMode`. If set `singleTabMode=fullwidth` then when there is only one tab in a group this tab will expand
to the entire width of the group. For example:
> This can be conmbined with <Link to="./dockview/#locked-group">Locked Groups</Link> to create an application that feels more like a Window Manager
> rather than a collection of groups and tabs.
```tsx
<DockviewReactComponent singleTabMode="fullwidth" {...otherProps} />
```
<DockviewNative />
### Example
hello
<DockviewNative2 />

View File

@ -11,7 +11,7 @@ type FeatureItem = {
const FeatureList: FeatureItem[] = [ const FeatureList: FeatureItem[] = [
{ {
title: '', title: '',
Svg: require('@site/static/img/dockview_grid_2.svg').default, Svg: require('@site/static/img/dockview_grid_3.svg').default,
description: ( description: (
<> <>
<div className="feature-banner"> <div className="feature-banner">

View File

@ -84,7 +84,7 @@ export const CustomHeadersDockview = () => {
position: { referencePanel: 'panel_7', direction: 'within' }, position: { referencePanel: 'panel_7', direction: 'within' },
}); });
event.api.addEmptyGroup(); event.api.addGroup();
}; };
return ( return (

View File

@ -209,9 +209,8 @@ const Icon = (props: {
}; };
const Button = () => { const Button = () => {
const [position, setPosition] = React.useState< const [position, setPosition] =
{ x: number; y: number } | undefined React.useState<{ x: number; y: number } | undefined>(undefined);
>(undefined);
const close = () => setPosition(undefined); const close = () => setPosition(undefined);
@ -316,19 +315,19 @@ export const DockviewDemo = () => {
position: { referencePanel: 'panel_7', direction: 'within' }, position: { referencePanel: 'panel_7', direction: 'within' },
}); });
event.api.addEmptyGroup(); event.api.addGroup();
event.api.getPanel('panel_1').api.setActive(); event.api.getPanel('panel_1').api.setActive();
setInterval(() => { // setInterval(() => {
event.api.getPanel('panel_1').update({ // event.api.getPanel('panel_1').update({
params: { // params: {
params: { // params: {
title: Date.now().toString(), // title: Date.now().toString(),
}, // },
}, // },
}); // });
}, 1000); // }, 1000);
}; };
return ( return (

View File

@ -0,0 +1,17 @@
.dockview-groupcontrol-demo {
height: 100%;
display: flex;
align-items: center;
color: white;
background-color: black;
padding-left: 8px;
.dockview-groupcontrol-demo-group-active {
padding: 0px 8px;
}
.dockview-groupcontrol-demo-active-panel {
color: yellow;
padding: 0px 8px;
}
}

View File

@ -0,0 +1,103 @@
import {} from '@site/../dockview/dist/cjs/dnd/droptarget';
import {
DockviewReact,
DockviewReadyEvent,
IDockviewGroupControlProps,
IDockviewPanelProps,
} from 'dockview';
import * as React from 'react';
import './groupControl.scss';
const components = {
default: (props: IDockviewPanelProps<{ title: string; x?: number }>) => {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: 'white',
height: '100%',
}}
>
<span>{`${props.params.title}`}</span>
{props.params.x && <span>{` ${props.params.x}`}</span>}
</div>
);
},
};
const GroupControlComponent = (props: IDockviewGroupControlProps) => {
const isGroupActive = props.isGroupActive;
const activePanel = props.activePanel;
return (
<div className="dockview-groupcontrol-demo">
<span
className="dockview-groupcontrol-demo-group-active"
style={{
background: isGroupActive ? 'green' : 'red',
}}
>
{isGroupActive ? 'Group Active' : 'Group Inactive'}
</span>
<span className="dockview-groupcontrol-demo-active-panel">{`activePanel: ${
activePanel?.id || 'null'
}`}</span>
</div>
);
};
export const DockviewGroupControl = () => {
const onReady = (event: DockviewReadyEvent) => {
const panel1 = event.api.addPanel({
id: 'panel_1',
component: 'default',
tabComponent: 'default',
params: {
title: 'Window 1',
},
});
const panel2 = event.api.addPanel({
id: 'panel_2',
component: 'default',
tabComponent: 'default',
params: {
title: 'Window 2',
},
position: {
direction: 'right',
},
});
const panel3 = event.api.addPanel({
id: 'panel_3',
component: 'default',
tabComponent: 'default',
params: {
title: 'Window 3',
},
position: {
direction: 'below',
},
});
};
return (
<div
style={{
height: '500px',
display: 'flex',
flexDirection: 'column',
}}
>
<DockviewReact
onReady={onReady}
components={components}
groupControlComponent={GroupControlComponent}
className="dockview-theme-abyss"
/>
</div>
);
};

View File

@ -0,0 +1,34 @@
.nested-dockview {
position: relative;
::after {
content: '';
position: absolute;
top: 0px;
left: 0px;
height: 1px;
width: 100%;
background-color: var(--dv-separator-border);
}
}
.header-title {
padding: 0px 8px;
}
.my-custom-tab {
padding: 0px 8px;
width: 100%;
display: flex;
height: 100%;
align-items: center;
background-color: var(--dv-tabs-and-actions-container-background-color);
.my-custom-tab-icon {
font-size: 16px;
&:hover {
border-radius: 2px;
background-color: var(--dv-icon-hover-background-color);
}
}
}

View File

@ -0,0 +1,259 @@
import {
CanDisplayOverlay,
Droptarget,
DropTargetDirections,
} from '@site/../dockview/dist/cjs/dnd/droptarget';
import {
DockviewDndOverlayEvent,
DockviewDropEvent,
DockviewReact,
DockviewReadyEvent,
GridviewReact,
GridviewReadyEvent,
IDockviewPanelProps,
IGridviewPanelProps,
Position,
Direction,
IDockviewPanelHeaderProps,
} from 'dockview';
import * as React from 'react';
import './native.scss';
class CustomDndTraget {
private data: any;
static SINGLETON = new CustomDndTraget();
setData<T>(t: T): void {
this.data = t;
}
getData<T>(): T {
return this.data;
}
clearData(): void {
this.data = null;
}
}
type CustomDescriptor = {
type: 'CUSTOM';
id: string;
};
function isCustomDescriptor(obj: any): obj is CustomDescriptor {
return (
typeof obj === 'object' && (obj as CustomDescriptor).type === 'CUSTOM'
);
}
function convertPositionToDirection(position: Position): Direction {
switch (position) {
case Position.Left:
return 'left';
case Position.Right:
return 'right';
case Position.Bottom:
return 'below';
case Position.Top:
return 'above';
case Position.Center:
return 'within';
}
}
const components = {
default: (props: IDockviewPanelProps<{ title: string; x?: number }>) => {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: 'white',
height: '100%',
}}
>
<span>{`${props.params.title}`}</span>
{props.params.x && <span>{` ${props.params.x}`}</span>}
</div>
);
},
isolatedApp: (
props: IDockviewPanelProps<{ title: string; x?: number }>
) => {
const onReady = (event: DockviewReadyEvent) => {
const panel1 = event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Tab 1',
},
});
const panel2 = event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Tab 2',
},
});
const panel3 = event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Tab 3',
},
});
};
return (
<DockviewReact
onReady={onReady}
components={components}
tabComponents={tabComponents}
className="dockview-theme-abyss"
/>
);
},
};
const tabComponents = {
default: (props: IDockviewPanelHeaderProps<{ title: string }>) => {
return (
<div className="my-custom-tab">
<span>{props.params.title}</span>
<span style={{ flexGrow: 1 }} />
<span className="my-custom-tab-icon material-symbols-outlined">
chrome_minimize
</span>
<span className="my-custom-tab-icon material-symbols-outlined">
chrome_maximize
</span>
<span className="my-custom-tab-icon material-symbols-outlined">
close
</span>
</div>
);
},
};
export const DockviewNative = () => {
const onReady = (event: DockviewReadyEvent) => {
const panel1 = event.api.addPanel({
id: 'panel_1',
component: 'default',
tabComponent: 'default',
params: {
title: 'Window 1',
},
});
panel1.group.locked = true;
const panel2 = event.api.addPanel({
id: 'panel_2',
component: 'default',
tabComponent: 'default',
params: {
title: 'Window 2',
},
position: {
direction: 'right',
},
});
panel2.group.locked = true;
const panel3 = event.api.addPanel({
id: 'panel_3',
component: 'default',
tabComponent: 'default',
params: {
title: 'Window 3',
},
position: {
direction: 'below',
},
});
panel3.group.locked = true;
};
return (
<div
style={{
height: '500px',
display: 'flex',
flexDirection: 'column',
}}
>
<DockviewReact
onReady={onReady}
components={components}
tabComponents={tabComponents}
className="dockview-theme-abyss"
singleTabMode="fullwidth"
/>
</div>
);
};
export const DockviewNative2 = () => {
const onReady = (event: DockviewReadyEvent) => {
const panel1 = event.api.addPanel({
id: 'panel_1',
component: 'isolatedApp',
tabComponent: 'default',
params: {
title: 'Window 1',
},
});
panel1.group.locked = true;
const panel2 = event.api.addPanel({
id: 'panel_2',
component: 'isolatedApp',
tabComponent: 'default',
params: {
title: 'Window 2',
},
position: {
direction: 'right',
},
});
panel2.group.locked = true;
const panel3 = event.api.addPanel({
id: 'panel_3',
component: 'isolatedApp',
tabComponent: 'default',
params: {
title: 'Window 3',
},
position: {
direction: 'below',
},
});
panel3.group.locked = true;
};
return (
<div
style={{
height: '500px',
display: 'flex',
flexDirection: 'column',
}}
>
<DockviewReact
onReady={onReady}
components={components}
tabComponents={tabComponents}
className="dockview-theme-abyss"
singleTabMode="fullwidth"
/>
</div>
);
};

View File

@ -13,10 +13,10 @@ const renderVisibleComponentsOnlyAtom = atom<boolean>({
default: false, default: false,
}); });
function RenderWhenVisible< function RenderWhenVisible(
T extends { api: Pick<PanelApi, 'isVisible' | 'onDidVisibilityChange'> } component: React.FunctionComponent<IDockviewPanelProps>
>(component: React.FunctionComponent<T>) { ) {
const HigherOrderComponent = (props: T) => { const HigherOrderComponent = (props: IDockviewPanelProps) => {
const [visible, setVisible] = React.useState<boolean>( const [visible, setVisible] = React.useState<boolean>(
props.api.isVisible props.api.isVisible
); );

View File

@ -23,13 +23,24 @@ const Default = (props: IDockviewPanelProps) => {
step={1} step={1}
/> />
<button <button
style={{ width: '100px' }}
onClick={() => { onClick={() => {
props.api.group.api.setSize({ props.api.group.api.setSize({
width, width,
}); });
}} }}
> >
Set Resize Group
</button>
<button
style={{ width: '100px' }}
onClick={() => {
props.api.setSize({
width,
});
}}
>
Resize panel
</button> </button>
</div> </div>
<div className="resize-control"> <div className="resize-control">
@ -42,13 +53,24 @@ const Default = (props: IDockviewPanelProps) => {
step={1} step={1}
/> />
<button <button
style={{ width: '100px' }}
onClick={() => { onClick={() => {
props.api.group.api.setSize({ props.api.group.api.setSize({
height, height,
}); });
}} }}
> >
Set Resize Group
</button>
<button
style={{ width: '100px' }}
onClick={() => {
props.api.setSize({
height,
});
}}
>
Resize Panel
</button> </button>
</div> </div>
</div> </div>

View File

@ -0,0 +1,20 @@
<svg width="156" height="18" viewBox="0 0 156 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="4" width="156" height="14" fill="#1C1C2A"/>
<rect y="4" width="30" height="14" fill="#10192C"/>
<rect x="31" y="4" width="30" height="14" fill="#10192C"/>
<rect x="62" y="4" width="30" height="14" fill="#000C18"/>
<rect x="30" y="4" width="1" height="14" fill="#2B2B4A"/>
<rect x="61" y="4" width="1" height="14" fill="#2B2B4A"/>
<rect x="92" y="4" width="1" height="14" fill="#2B2B4A"/>
<rect x="66" y="9" width="7" height="4" rx="2" fill="white"/>
<rect x="76" y="9" width="12" height="4" rx="2" fill="white"/>
<rect x="33" y="9" width="15" height="4" rx="2" fill="#777777"/>
<rect x="2" y="9" width="6" height="4" rx="2" fill="#777777"/>
<rect x="10" y="9" width="18" height="4" rx="2" fill="#777777"/>
<rect x="93" y="4" width="63" height="14" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="111.5" y="0.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="115" y="5" width="7" height="4" rx="2" fill="white"/>
<rect x="126" y="5" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.344 13.2654C128.52 13.1426 128.51 12.8799 128.327 12.77L124.18 10.2905C123.961 10.1595 123.691 10.3492 123.739 10.5997L124.651 15.344C124.691 15.5542 124.935 15.653 125.11 15.5302L125.772 15.067C125.867 15.0002 125.914 14.8836 125.892 14.7693L125.602 13.2612C125.554 13.0106 125.825 12.821 126.044 12.952L127.362 13.7401C127.462 13.7999 127.588 13.7954 127.683 13.7286L128.344 13.2654Z" fill="white"/>
<rect x="127.5" y="14.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,25 @@
<svg width="156" height="18" viewBox="0 0 156 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="4" width="156" height="14" fill="#1C1C2A"/>
<rect y="4" width="30" height="14" fill="#10192C"/>
<rect x="31" y="4" width="30" height="14" fill="#10192C"/>
<rect x="62" y="4" width="30" height="14" fill="#000C18"/>
<rect x="30" y="4" width="1" height="14" fill="#2B2B4A"/>
<rect x="61" y="4" width="1" height="14" fill="#2B2B4A"/>
<rect x="92" y="4" width="1" height="14" fill="#2B2B4A"/>
<rect x="66" y="9" width="7" height="4" rx="2" fill="#777777"/>
<rect x="76" y="9" width="12" height="4" rx="2" fill="#777777"/>
<rect x="33" y="9" width="15" height="4" rx="2" fill="#282828"/>
<rect x="2" y="9" width="6" height="4" rx="2" fill="#282828"/>
<rect x="10" y="9" width="18" height="4" rx="2" fill="#282828"/>
<rect x="59" width="91" height="10" fill="#2B2B4A"/>
<rect x="60" y="1" width="89.1429" height="8" fill="#1C1C2A"/>
<rect x="60" y="1" width="17.1429" height="8" fill="#10192C"/>
<rect x="77.7142" y="1" width="17.1429" height="8" fill="#10192C"/>
<rect x="77.1428" y="1" width="0.571429" height="8" fill="#2B2B4A"/>
<rect x="94.8572" y="1" width="0.571429" height="8" fill="#2B2B4A"/>
<rect x="78.8572" y="3.85718" width="8.57143" height="2.28571" rx="1.14286" fill="white"/>
<rect x="89.1428" y="3.85718" width="2.85714" height="2.28571" rx="1.14286" fill="white"/>
<rect x="61.1428" y="3.85718" width="10.8571" height="2.28571" rx="1.14286" fill="#777777"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M117.344 8.26544C117.52 8.14263 117.51 7.87986 117.327 7.76998L113.18 5.29049C112.961 5.15953 112.691 5.34916 112.739 5.59974L113.651 10.344C113.691 10.5542 113.935 10.653 114.11 10.5302L114.772 10.067C114.867 10.0002 114.914 9.88362 114.892 9.76929L114.602 8.26123C114.554 8.01064 114.825 7.82101 115.044 7.95197L116.362 8.74015C116.462 8.79989 116.588 8.79538 116.683 8.7286L117.344 8.26544Z" fill="white"/>
<rect x="116.5" y="9.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

20
packages/docs/static/img/add_to_tab.svg vendored Normal file
View File

@ -0,0 +1,20 @@
<svg width="156" height="18" viewBox="0 0 156 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="4" width="156" height="14" fill="#1C1C2A"/>
<rect y="4" width="30" height="14" fill="#10192C"/>
<rect x="31" y="4" width="30" height="14" fill="#10192C"/>
<rect x="62" y="4" width="30" height="14" fill="#000C18"/>
<rect x="30" y="4" width="1" height="14" fill="#2B2B4A"/>
<rect x="61" y="4" width="1" height="14" fill="#2B2B4A"/>
<rect x="92" y="4" width="1" height="14" fill="#2B2B4A"/>
<rect x="66" y="9" width="7" height="4" rx="2" fill="white"/>
<rect x="76" y="9" width="12" height="4" rx="2" fill="white"/>
<rect x="33" y="9" width="15" height="4" rx="2" fill="#777777"/>
<rect x="2" y="9" width="6" height="4" rx="2" fill="#777777"/>
<rect x="10" y="9" width="18" height="4" rx="2" fill="#777777"/>
<path d="M31 4H61V18H31V4Z" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="49.5" y="0.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="53" y="5" width="7" height="4" rx="2" fill="white"/>
<rect x="64" y="5" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M66.3445 13.2654C66.5198 13.1426 66.5104 12.8799 66.3266 12.77L62.1804 10.2905C61.9614 10.1595 61.6906 10.3492 61.7388 10.5997L62.6506 15.344C62.691 15.5542 62.9347 15.653 63.1101 15.5302L63.7716 15.067C63.8669 15.0002 63.9142 14.8836 63.8922 14.7693L63.6024 13.2612C63.5542 13.0106 63.825 12.821 64.044 12.952L65.362 13.7401C65.4619 13.7999 65.5876 13.7954 65.683 13.7286L66.3445 13.2654Z" fill="white"/>
<rect x="65.5" y="14.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,45 @@
<svg width="156" height="121" viewBox="0 0 156 121" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="156" height="14" fill="#1C1C2A"/>
<rect width="30" height="14" fill="#10192C"/>
<rect x="31" width="30" height="14" fill="#10192C"/>
<rect x="62" width="30" height="14" fill="#000C18"/>
<rect x="30" width="1" height="14" fill="#2B2B4A"/>
<rect x="61" width="1" height="14" fill="#2B2B4A"/>
<rect x="92" width="1" height="14" fill="#2B2B4A"/>
<rect x="66" y="5" width="7" height="4" rx="2" fill="white"/>
<rect x="76" y="5" width="12" height="4" rx="2" fill="white"/>
<rect x="33" y="5" width="15" height="4" rx="2" fill="#777777"/>
<rect x="2" y="5" width="6" height="4" rx="2" fill="#777777"/>
<rect x="10" y="5" width="18" height="4" rx="2" fill="#777777"/>
<rect y="14" width="156" height="107" fill="#000C18"/>
<rect x="38" y="14" width="80" height="25" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="38" y="96" width="80" height="25" fill="#E1E1E1" fill-opacity="0.25"/>
<rect y="29" width="30" height="77" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="38" y="48" width="80" height="38" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="126" y="29" width="30" height="77" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="63.5" y="20.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="67" y="25" width="7" height="4" rx="2" fill="white"/>
<rect x="78" y="25" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M80.3445 33.2654C80.5198 33.1426 80.5104 32.8799 80.3266 32.77L76.1804 30.2905C75.9614 30.1595 75.6906 30.3492 75.7388 30.5997L76.6506 35.344C76.691 35.5542 76.9347 35.653 77.1101 35.5302L77.7716 35.067C77.8669 35.0002 77.9142 34.8836 77.8922 34.7693L77.6024 33.2612C77.5542 33.0106 77.825 32.821 78.044 32.952L79.362 33.7401C79.4619 33.7999 79.5876 33.7954 79.683 33.7286L80.3445 33.2654Z" fill="white"/>
<rect x="79.5" y="34.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<rect x="122.5" y="62.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="126" y="67" width="7" height="4" rx="2" fill="white"/>
<rect x="137" y="67" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M139.344 75.2654C139.52 75.1426 139.51 74.8799 139.327 74.77L135.18 72.2905C134.961 72.1595 134.691 72.3492 134.739 72.5997L135.651 77.344C135.691 77.5542 135.935 77.653 136.11 77.5302L136.772 77.067C136.867 77.0002 136.914 76.8836 136.892 76.7693L136.602 75.2612C136.554 75.0106 136.825 74.821 137.044 74.952L138.362 75.7401C138.462 75.7999 138.588 75.7954 138.683 75.7286L139.344 75.2654Z" fill="white"/>
<rect x="138.5" y="76.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<rect x="62.5" y="100.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="66" y="105" width="7" height="4" rx="2" fill="white"/>
<rect x="77" y="105" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M79.3445 113.265C79.5198 113.143 79.5104 112.88 79.3266 112.77L75.1804 110.29C74.9614 110.16 74.6906 110.349 74.7388 110.6L75.6506 115.344C75.691 115.554 75.9347 115.653 76.1101 115.53L76.7716 115.067C76.8669 115 76.9142 114.884 76.8922 114.769L76.6024 113.261C76.5542 113.011 76.825 112.821 77.044 112.952L78.362 113.74C78.4619 113.8 78.5876 113.795 78.683 113.729L79.3445 113.265Z" fill="white"/>
<rect x="78.5" y="114.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<rect x="4.5" y="62.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="8" y="67" width="7" height="4" rx="2" fill="white"/>
<rect x="19" y="67" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.3445 75.2654C21.5198 75.1426 21.5104 74.8799 21.3266 74.77L17.1804 72.2905C16.9614 72.1595 16.6906 72.3492 16.7388 72.5997L17.6506 77.344C17.691 77.5542 17.9347 77.653 18.1101 77.5302L18.7716 77.067C18.8669 77.0002 18.9142 76.8836 18.8922 76.7693L18.6024 75.2612C18.5542 75.0106 18.825 74.821 19.044 74.952L20.362 75.7401C20.4619 75.7999 20.5876 75.7954 20.683 75.7286L21.3445 75.2654Z" fill="white"/>
<rect x="20.5" y="76.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<rect x="63.5" y="62.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="67" y="67" width="7" height="4" rx="2" fill="white"/>
<rect x="78" y="67" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M80.3445 75.2654C80.5198 75.1426 80.5104 74.8799 80.3266 74.77L76.1804 72.2905C75.9614 72.1595 75.6906 72.3492 75.7388 72.5997L76.6506 77.344C76.691 77.5542 76.9347 77.653 77.1101 77.5302L77.7716 77.067C77.8669 77.0002 77.9142 76.8836 77.8922 76.7693L77.6024 75.2612C77.5542 75.0106 77.825 74.821 78.044 74.952L79.362 75.7401C79.4619 75.7999 79.5876 75.7954 79.683 75.7286L80.3445 75.2654Z" fill="white"/>
<rect x="79.5" y="76.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,53 @@
<svg width="163" height="131" viewBox="0 0 163 131" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 5C4 2.23858 6.23858 0 9 0H154C156.761 0 159 2.23858 159 5V11H4V5Z" fill="#DCDCDC"/>
<rect x="4" y="10" width="155" height="1" fill="#BABABA"/>
<rect x="4" y="11" width="155" height="61" fill="#000C18"/>
<rect x="81" y="73" width="78" height="58" fill="#000C18"/>
<rect x="4" y="73" width="77" height="58" fill="#000C18"/>
<rect x="4" y="72" width="155" height="1" fill="#2B2B4A"/>
<path d="M81 73H80V131H81V73Z" fill="#2B2B4A"/>
<rect x="4" y="11" width="155" height="14" fill="#1C1C2A"/>
<rect x="4" y="11" width="30" height="14" fill="#10192C"/>
<rect x="35" y="11" width="30" height="14" fill="#000C18"/>
<rect x="34" y="11" width="1" height="14" fill="#2B2B4A"/>
<rect x="65" y="11" width="1" height="14" fill="#2B2B4A"/>
<rect x="81" y="73" width="78" height="14" fill="#1C1C2A"/>
<rect x="81" y="73" width="24" height="14" fill="#10192C"/>
<rect x="105" y="73" width="24" height="14" fill="#000C18"/>
<rect x="105" y="73" width="0.503226" height="14" fill="#2B2B4A"/>
<rect x="129" y="73" width="0.503226" height="14" fill="#2B2B4A"/>
<rect x="38" y="16" width="12" height="4" rx="2" fill="#777777"/>
<rect x="107" y="78" width="7" height="4" rx="2" fill="#777777"/>
<rect x="115" y="78" width="11" height="4" rx="2" fill="#777777"/>
<rect x="53" y="16" width="4" height="4" rx="2" fill="#777777"/>
<rect x="7" y="16" width="5" height="4" rx="2" fill="#282828"/>
<rect x="13" y="16" width="16" height="4" rx="2" fill="#282828"/>
<rect x="84" y="78" width="16" height="4" rx="2" fill="#282828"/>
<rect x="8" y="3" width="4" height="4" rx="2" fill="#FD605E"/>
<rect x="14" y="3" width="4" height="4" rx="2" fill="#FBBC3F"/>
<rect x="20" y="3" width="4" height="4" rx="2" fill="#34C942"/>
<rect x="149" y="34" width="10" height="77" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="4" y="34" width="10" height="77" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="18" y="11" width="124" height="10" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="18" y="121" width="124" height="10" fill="#E1E1E1" fill-opacity="0.25"/>
<rect x="90.5" y="112.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="94" y="117" width="7" height="4" rx="2" fill="white"/>
<rect x="105" y="117" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M107.344 125.265C107.52 125.143 107.51 124.88 107.327 124.77L103.18 122.29C102.961 122.16 102.691 122.349 102.739 122.6L103.651 127.344C103.691 127.554 103.935 127.653 104.11 127.53L104.772 127.067C104.867 127 104.914 126.884 104.892 126.769L104.602 125.261C104.554 125.011 104.825 124.821 105.044 124.952L106.362 125.74C106.462 125.8 106.588 125.795 106.683 125.729L107.344 125.265Z" fill="white"/>
<rect x="106.5" y="126.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<rect x="0.5" y="67.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="4" y="72" width="7" height="4" rx="2" fill="white"/>
<rect x="15" y="72" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.3445 80.2654C17.5198 80.1426 17.5104 79.8799 17.3266 79.77L13.1804 77.2905C12.9614 77.1595 12.6906 77.3492 12.7388 77.5997L13.6506 82.344C13.691 82.5542 13.9347 82.653 14.1101 82.5302L14.7716 82.067C14.8669 82.0002 14.9142 81.8836 14.8922 81.7693L14.6024 80.2612C14.5542 80.0106 14.825 79.821 15.044 79.952L16.362 80.7401C16.4619 80.7999 16.5876 80.7954 16.683 80.7286L17.3445 80.2654Z" fill="white"/>
<rect x="16.5" y="81.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<rect x="133.5" y="50.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="137" y="55" width="7" height="4" rx="2" fill="white"/>
<rect x="148" y="55" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M150.344 63.2654C150.52 63.1426 150.51 62.8799 150.327 62.77L146.18 60.2905C145.961 60.1595 145.691 60.3492 145.739 60.5997L146.651 65.344C146.691 65.5542 146.935 65.653 147.11 65.5302L147.772 65.067C147.867 65.0002 147.914 64.8836 147.892 64.7693L147.602 63.2612C147.554 63.0106 147.825 62.821 148.044 62.952L149.362 63.7401C149.462 63.7999 149.588 63.7954 149.683 63.7286L150.344 63.2654Z" fill="white"/>
<rect x="149.5" y="64.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<rect x="84.5" y="14.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
<rect x="88" y="19" width="7" height="4" rx="2" fill="white"/>
<rect x="99" y="19" width="12" height="4" rx="2" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.344 27.2654C101.52 27.1426 101.51 26.8799 101.327 26.77L97.1804 24.2905C96.9614 24.1595 96.6906 24.3492 96.7388 24.5997L97.6506 29.344C97.691 29.5542 97.9347 29.653 98.1101 29.5302L98.7716 29.067C98.8669 29.0002 98.9142 28.8836 98.8922 28.7693L98.6024 27.2612C98.5542 27.0106 98.825 26.821 99.044 26.952L100.362 27.7401C100.462 27.7999 100.588 27.7954 100.683 27.7286L101.344 27.2654Z" fill="white"/>
<rect x="100.5" y="28.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB