# Floating Windows

A component that provides a windowing interface for browser content.
We provide the windows, you provide the content.

# Screenshots

## In Browser

<img src="./readme/image1.png" width="400" >

## Popped out

<img src="./readme/image2.png" width="400" >

## Interaction

<img src="./readme/floating-window.gif" width="400" >

## Features

- Can open multiple windows at a time.
- Can drag windows around inside the browser window by dragging on title bar.
- Re-size windows inside the browser window by dragging window edges.
- Pop out window, so the content is inside a separate browser window.
- Pop in window, so the popped out window returns to the original browser window.
- Click on a window to bring to front.
- Has a title bar, with title and action buttons.
- Has a close button and pop out button in title bar.
- Windows can start "popped out".
- Event listener, so you can hook your own code into UI events.

## Planned Features

- Provision for other controls inside the header.
- Not tied to LUI for styles/components (you can provide styles and/or components)
- Some way of allowing this control to work in a mobile setting (either disabled, or mobile aware?)
- Style agnostic - style how you want.
- Additional content in title area.
- Ability to set the base Z value.
- Able to get/set entire windows state, so application can, say, store to local storage.
- Can define initial defaults to the context.
- Beefing up the event system (more events, multiple listeners.)
- Listeners can cancel events.
- Ability to open the window as a draggable window, a panel attached to the side, or a panel attached to the bottom.
- Keyboard controls.

<div style="page-break-after: always;"></div>

# Installing

```bash
npm install --save @linzjs/floating-window
```

# Quick Example

### Surround your app content with the window context provider.

```typescript
import { FloatingWindowContextProvider } from '@linzjs/floating-windows';

export const App = () => {
  return (
    <FloatingWindowContextProvider>
      <AppPage />
    </FloatingWindowContextProvider>
  );
};

// Name of window (we use this below)
const windowAName = 'Win A';
```

<div style="page-break-after: always;"></div>

### Create a floating window component with your content as children

```typescript
import { FloatingWindow } from '@linzjs/floating-windows';

export const FloatingWindowExample = () => {
  return (
    <FloatingWindow
      name={windowAName}
      title={'This is window A'}
      initialSize={{ width: 400, height: 400 }}
      initialPosition={{ x: 100, y: 100 }}
    >
      <div>Lorem ipsum dolor sit amet, consectetur...</div>
    </FloatingWindow>
  );
};
```

The content is initially not displayed. To display it, you'll need to show the window.
Here's a button that does just that (for example.) It uses the context to show and hide the window,
and to find out if the window is currently open or not.

## A button to show/hide the window

```typescript
import { useFloatingWindow } from '@linzjs/floating-windows';

const TestButton = (props: { windowName: string }): JSX.Element => {
  const { showWindow, hideWindow, isWindowOpen } = useFloatingWindow();

  const isOpen = isWindowOpen(props.windowName);
  return (
    <button
      style={{ color: isOpen ? 'red' : 'green' }}
      onClick={() => {
        if (isOpen) {
          hideWindow(props.windowName);
        } else {
          showWindow(props.windowName);
        }
      }}
    >
      {props.windowName}
    </button>
  );
};
```

<div style="page-break-after: always;"></div>

# API

Notes:

- Windows are identified by a unique internal name, which can be different to the displayed title.

# Window Context

```typescript
export type FloatingWindowContextType = {
  setWindowDefaults: (windowName: string, windowProperties: FloatingWindowProperties) => void;
  getOpenWindowNames: () => string[];
  isWindowOpen: (windowName: string) => boolean;
  isWindowPoppedOut: (windowName: string) => boolean;
  showWindow: (windowName: string, showPoppedOut?: boolean) => void;
  hideWindow: (windowName: string | string[]) => void;
  bringWindowToTop: (windowName: string) => void;
  getTopWindowName: () => string | null;
  getWindowPosition: (windowName: string) => FloatingWindowPosition;
  setWindowPosition: (windowName: string, position: FloatingWindowPosition) => void;
  getPositionalStyle: (windowName: string) => CSS.Properties;
  getWindowSize: (windowName: string) => FloatingWindowSize;
  setWindowSize: (windowName: string, dimensions: FloatingWindowSize) => void;
  externalWindowsDOM: Record<string, Document>;
  setExternalWindowsDOM: React.Dispatch<React.SetStateAction<Record<string, Document>>>;
  popWindowOut: (windowName: string) => void;
  popWindowIn: (windowName: string) => void;
  getZ: (windowName: string) => number;
  addEventListener: (
    eventType: FloatingWindowEventType,
    windowName: string,
    eventCallback: FloatingWindowCallback,
  ) => void;
  removeEventListener: (eventType: FloatingWindowEventType, windowName: string) => void;
};
```

| Method                                                                                                                      | Description                                                                   |
| --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `setWindowDefaults: (windowName: string, windowProperties: FloatingWindowProperties) => void`                               | Set initial defaults for windows.                                             |
| `getOpenWindowNames: () => string[]`                                                                                        | Return a list of the open window names.                                       |
| `isWindowOpen: (windowName: string) => boolean`                                                                             | Returns true if the window named `windowName` is open. False otherwise        |
| `isWindowPoppedOut: (windowName: string) => boolean`                                                                        | Returns true if the window named `windowName` is popped out. False otherwise. |
| `showWindow: (windowName: string, showPoppedOut?: boolean) => void`                                                         | Show window named `windowName`, bringing it to the front.                     |
| `hideWindow: (windowName: string) => void`                                                                                  | Hide window named `windowName`, closing it.                                   |
| `bringWindowToTop: (windowName: string) => void`                                                                            | Bring the window named `windowName` to the front of the display               |
| `getTopWindowName: () => string \| null`                                                                                    | Return the name of the top-most window, or null if there is none.             |
| `getWindowPosition: (windowName: string) => FloatingWindowPosition`                                                         | Return the position of the window named `windowName`                          |
| `setWindowPosition: (windowName: string, position: FloatingWindowPosition) => void`                                         | Set the position of the window `windowName`                                   |
| `getPositionalStyle: (windowName: string) => CSS.Properties`                                                                | Return a CSS style that includes the window position for window `windowName`  |
| `getWindowSize: (windowName: string) => FloatingWindowSize`                                                                 | Return the size of the window named `windowName`                              |
| `setWindowSize: (windowName: string, dimensions: FloatingWindowSize) => void`                                               | Set the size of the window called `windowName`                                |
| `externalWindowsDOM: Record<string, Document>;`                                                                             |                                                                               |
| `setExternalWindowsDOM: React.Dispatch<React.SetStateAction<Record<string, Document>>>`                                     |                                                                               |
| `popWindowOut: (windowName: string) => void`                                                                                | Pop the window named `windowName` out into it's own window.                   |
| `popWindowIn: (windowName: string) => void`                                                                                 | Pop the window named `windowName` back into the browser                       |
| `getZ: (windowName: string) => number`                                                                                      | Return the Z index of the window named `windowName`                           |
| `addEventListener: (eventType: FloatingWindowEventType, windowName: string, eventCallback: FloatingWindowCallback) => void` | See below                                                                     |
| `removeEventListener: (eventType: FloatingWindowEventType, windowName: string) => void`                                     | See below                                                                     |

# Components

## FloatingWindow

### Props

```typescript
interface FloatingWindowProps {
  name: string;
  title?: string;
  children?: JSX.Element;
  extraHeaderButtons?: ReactNode;
  initialPosition: FloatingWindowPosition;
  initialSize: FloatingWindowSize;
  startPoppedOut?: boolean;
}
```

| Prop                                      | Description                                                                                                                         |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `name: string`                            | Unique (internal) name of this window                                                                                               |
| `title?: string`                          | The displayed title in the window. No title bar is displayed if the title is not given.                                             |
| `children?: JSX.Element`                  | The window content.                                                                                                                 |
| `extraHeaderButtons?: ReactNode`          | Any extra buttons in the header.                                                                                                    |
| `initialPosition: FloatingWindowPosition` | Initial position (X,Y coordinates.)                                                                                                 |
| `initialSize: FloatingWindowSize`         | Initial size (width and height.)                                                                                                    |
| `startPoppedOut?: boolean`                | set to true to start the window popped out. False to keep in in the browser, and undefined to use the last state the window was in. |

# Events

The window context has the ability to call user-supplied callbacks when internal events happen,
such as windows being popped out.
The `addEventListener` method allows you to add a listener to an event, and `removeEventListener` removes it.
A listener listens for an event type for a particular window, and you specify both when adding the listener.

```typescript
addEventListener(showWindow, 'windowA', (event) => {
  console.log(`${event.windowName} shown`);
});
```

## Event Types

```typescript
enum FloatingWindowEventType {
  showWindow,
  hideWindow,
  popoutWindow,
}
```

| Event          | Description                 |
| -------------- | --------------------------- |
| `showWindow`   | When a window is shown      |
| `hideWindow`   | When a window is hidden     |
| `popoutWindow` | When a window is popped out |
