Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ export default defineConfig({
{ text: "Text Styles", link: "/typescript/styling/text-styles" }
]
},
{
text: "UI Components",
collapsed: false,
items: [
{
text: "Ready-made Style Panel",
link: "/typescript/ui-components/style-panel"
}
]
},
{
text: "Managing Annotations",
collapsed: false,
Expand Down Expand Up @@ -215,6 +225,10 @@ export default defineConfig({
text: "Building UI Components",
collapsed: false,
items: [
{
text: "Ready-made UI",
link: "/react/ui-components/ready-made"
},
{ text: "Toolbar", link: "/react/ui-components/toolbar" },
{
text: "Style Panel",
Expand Down
Binary file added docs/public/ui/.DS_Store
Binary file not shown.
17 changes: 17 additions & 0 deletions docs/public/ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# UI control-panel screenshots

Drop the screenshots here. They are referenced from the docs with absolute
paths (VitePress serves `docs/public/` at the site root), e.g. `/ui/add-menu.png`.

Expected files (PNG, retina / 2x where possible, trimmed to the control with a
little padding):

| File | Shot | Used in |
| --- | --- | --- |
| `react-editor.png` | Full React editor: `AddMenu` toolbar + `ViewControls` + style panel over an annotated graph | `react/ui-components/ready-made.md` |
| `add-menu.png` | The `AddMenu` toolbar on its own | `react/ui-components/ready-made.md` |
| `panel-arrow.png` | Style panel with an **arrow** selected (color, extremities, stroke width, line type) | `react/ui-components/ready-made.md` |
| `panel-text.png` | Style panel with a **text** annotation selected (color, background, font, font size, stroke width, line type) | `react/ui-components/ready-made.md` |
| `vanilla-panel.png` | The vanilla `AnnotationPanel` (any annotation type) | `typescript/ui-components/style-panel.md` |

Once added, delete this README if you like — it isn't referenced anywhere.
Binary file added docs/public/ui/add-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/ui/panel-arrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/ui/panel-text.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/ui/react-editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/ui/vanilla-panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
180 changes: 180 additions & 0 deletions docs/react/ui-components/ready-made.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Ready-made UI Components

The React package ships a set of styled, themeable UI components so you don't
have to build a toolbar and style panel from scratch. They live under the
`@linkurious/ogma-annotations-react/ui` subpath and are completely optional —
importing the main package entry stays headless and pulls in none of this code
or CSS.

> Prefer to build your own? See [Building a Toolbar](./toolbar),
> [Building a Style Panel](./style-panel) and
> [Annotation List](./annotation-list). The ready-made components are built on
> the same `useAnnotationsContext` hook, so you can mix and match.

## Installation

The components are part of the package — no extra install. Import them from the
`/ui` subpath and load the stylesheet once in your app:

```tsx
import {
AnnotationPanelController,
AddMenu,
ViewControls
} from "@linkurious/ogma-annotations-react/ui";

// Load once, e.g. in your entry file:
import "@linkurious/ogma-annotations-react/ui/styles.css";
```

::: tip Why a separate subpath?
Keeping the UI on `/ui` means consumers who only want the headless editor pay
nothing for the styled components or their CSS. The icons are inline SVG, so
there is no icon font or extra icon dependency to wire up.
:::

## Putting it together

Drop the components inside an `AnnotationsContextProvider` (see
[the provider docs](../core-concepts/provider)):

```tsx
import { Ogma } from "@linkurious/ogma-react";
import { AnnotationsContextProvider } from "@linkurious/ogma-annotations-react";
import {
AnnotationPanelController,
AddMenu,
ViewControls
} from "@linkurious/ogma-annotations-react/ui";
import "@linkurious/ogma-annotations-react/ui/styles.css";

export function Editor({ graph }) {
return (
<Ogma graph={graph}>
<AnnotationsContextProvider>
{/* Toolbar: add arrow/text/box/polygon/comment, undo/redo, delete */}
<AddMenu />

{/* Center / rotate the view */}
<ViewControls />

{/* Style panel that follows the current selection */}
<AnnotationPanelController />
</AnnotationsContextProvider>
</Ogma>
);
}
```

That's a full editor: a drawing toolbar, view controls, and a style panel that
appears when you select an annotation and lets you edit color, background,
font, line type, stroke width, and arrow extremities.

<img src="/ui/react-editor.png" alt="The ready-made editor: AddMenu toolbar, ViewControls, and the style panel over an annotated graph" />

## Components

### `AnnotationPanelController`

A turnkey style panel. It listens to the editor's selection and shows the
[`AnnotationPanel`](#annotationpanel) for the selected annotation, hiding it
during drags and when nothing is selected. No props.

```tsx
<AnnotationPanelController />
```

<div style="display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-start;">
<figure style="margin: 0;">
<img src="/ui/panel-arrow.png" alt="Style panel for a selected arrow: color, extremities, stroke width, line type" style="max-width: 240px;" />
<figcaption>Arrow selected</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/ui/panel-text.png" alt="Style panel for selected text: color, background, font, font size, stroke width, line type" style="max-width: 240px;" />
<figcaption>Text selected</figcaption>
</figure>
</div>

If you want to control rendering yourself, use the `useAnnotationPanel` hook,
which returns the current `annotation` and `visible` state:

```tsx
import { useAnnotationPanel, AnnotationPanel } from "@linkurious/ogma-annotations-react/ui";

function MyPanel() {
const { annotation, visible } = useAnnotationPanel();
return <AnnotationPanel visible={visible} annotation={annotation} />;
}
```

### `AnnotationPanel`

The presentational style panel. Renders the right set of controls for the given
annotation type (arrow, text/box/comment, or polygon). You normally render it
via `AnnotationPanelController`, but you can drive it directly.

| Prop | Type | Description |
| --- | --- | --- |
| `annotation` | `Annotation \| null` | The annotation to edit. |
| `visible` | `boolean` | Whether the panel is shown. |

### `AddMenu`

The drawing toolbar: buttons for arrow, comment, box, text and polygon, plus
undo / redo and delete. Optional export buttons appear only when you pass the
corresponding callback.

<img src="/ui/add-menu.png" alt="AddMenu toolbar with drawing, history, delete and export buttons" />

| Prop | Type | Description |
| --- | --- | --- |
| `onJsonExport` | `() => void` _(optional)_ | Show a JSON export button that calls this. |
| `onSvgExport` | `() => void` _(optional)_ | Show an SVG export button that calls this. |

```tsx
<AddMenu onJsonExport={exportJson} onSvgExport={exportSvg} />
```

### `ViewControls`

Center-view and rotate-view buttons. Uses the Ogma instance from context; no
props.

### Field controllers

The panel is composed from small controllers you can reuse to build a custom
panel: `ColorController`, `BackgroundController`, `FontController`,
`ExtremityController`, `SliderController`, `LineTypeController`. Each takes the
target `annotation` and renders one section.

### `Icon`

The inline-SVG icon used throughout the components, in case you want to match
the look in your own UI.

| Prop | Type | Description |
| --- | --- | --- |
| `name` | `IconName` | One of the shared icon names. |
| `size` | `number` _(optional, default 18)_ | Width/height in px. |
| `rotate` | `boolean` _(optional)_ | Rotate 180°. |
| `className` | `string` _(optional)_ | Extra class. |

## Theming

The stylesheet is driven by CSS custom properties. Override them on the panel
(or any ancestor) to restyle without forking the CSS — the defaults preserve the
out-of-the-box look:

```css
.annotation-panel {
--oa-accent: #0aa; /* active state, selected option, slider thumb, etc. */
--oa-panel-bg: #fff;
--oa-panel-radius: 10px;
--oa-control-bg: #eee;
--oa-text-color: #333;
--oa-muted-color: #666;
}
```

The accent also falls back to a legacy `--brand` variable if you already set
one in your app.
104 changes: 104 additions & 0 deletions docs/typescript/ui-components/style-panel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Ready-made Style Panel

The core package ships a styled, framework-agnostic style panel so you don't
have to build one by hand. It lives under the `@linkurious/ogma-annotations/ui`
subpath and is completely optional — importing the main package entry stays
headless and pulls in none of this code or CSS.

## Installation

The panel is part of the package — no extra install. Import it from the `/ui`
subpath and load the stylesheet once:

```ts
import { AnnotationPanel } from "@linkurious/ogma-annotations/ui";
import "@linkurious/ogma-annotations/ui/styles.css";
```

::: tip Why a separate subpath?
Keeping the UI on `/ui` means consumers who only want the headless editor pay
nothing for the styled panel or its CSS. The icons are inline SVG, so there is
no icon font to wire up.
:::

## Usage

Create the panel with your `Control` instance. The panel wires itself to the
control's selection events: it appears when a single annotation is selected
(after the click/drag settles) and hides otherwise. It creates and manages its
own DOM inside the container you give it (defaulting to `document.body`).

```ts
import { Control } from "@linkurious/ogma-annotations";
import { AnnotationPanel } from "@linkurious/ogma-annotations/ui";
import "@linkurious/ogma-annotations/ui/styles.css";

const control = new Control(ogma);

const panel = new AnnotationPanel({
control,
container: document.getElementById("app")! // optional, defaults to document.body
});

// When tearing down:
panel.destroy();
```

<img src="/ui/vanilla-panel.png" alt="The vanilla style panel showing color, background, font and stroke controls for a selected annotation" />

The panel renders the right controls for the selected annotation type:

- **Arrow** — color, head/tail extremities, stroke width, line type.
- **Text / Box / Comment** — color, background, font, font size, stroke width,
line type.
- **Polygon** — color, fill, stroke width, line type.

Editing a control calls `control.updateStyle(...)` for the selected annotation,
so changes flow through the normal update path (including undo/redo).

## Options

| Option | Type | Description |
| --- | --- | --- |
| `control` | `Control` | The annotation controller to bind to. |
| `container` | `HTMLElement` _(optional)_ | Element the panel mounts into. Defaults to `document.body`. |

## Methods

| Method | Description |
| --- | --- |
| `show()` | Show the panel. |
| `hide()` | Hide the panel and close any open color picker. |
| `destroy()` | Detach all event listeners and remove the panel from the DOM. |

## Theming

The stylesheet is driven by CSS custom properties. Override them on the panel
(or any ancestor) to restyle without forking the CSS — the defaults preserve the
out-of-the-box look:

```css
.annotation-panel {
--oa-accent: #0aa; /* active state, selected option, slider thumb, etc. */
--oa-panel-bg: #fff;
--oa-panel-radius: 10px;
--oa-control-bg: #eee;
--oa-text-color: #333;
--oa-muted-color: #666;
}
```

The accent also falls back to a legacy `--brand` variable if you already set
one in your app.

## Building your own

The `/ui` subpath also exports the building blocks the panel uses, so you can
assemble a custom panel or reuse pieces:

- `BACKGROUNDS`, `FONTS`, `EXTREMITY_OPTIONS`, `LINE_TYPES` — the option config.
- `initialRecentColors`, `withColorFromAnnotation`, `rgbaToString` — recent-color
state helpers.
- `svgIcon`, `ICON_PATHS`, `IconName` — the inline SVG icon set.
- `attachPanelVisibility(control, { onShow, onHide })` — the selection → show/hide
state machine the panel is built on. Returns a `detach()` cleanup function.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 19 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@
"require": "./dist/index.js",
"default": "./dist/index.mjs"
},
"./ui": {
"types": "./dist/types/ui.d.ts",
"import": "./dist/ui.mjs",
"require": "./dist/ui.js",
"default": "./dist/ui.mjs"
},
"./ui/styles.css": {
"style": {
"default": "./dist/ui-styles.css"
},
"import": {
"default": "./dist/ui-styles.css"
},
"require": {
"default": "./dist/ui-styles.css"
}
},
"./style.css": {
"style": {
"default": "./src/style.css"
Expand Down Expand Up @@ -51,6 +68,7 @@
"bump:minor": "bump2version minor && npm version --no-git-tag-version minor",
"bump:major": "bump2version major && npm version --no-git-tag-version major",
"build": "vite build",
"postbuild": "cp src/ui/styles.css dist/ui-styles.css",
"lint": "npx oxlint -c oxlint.json src",
"lint:ci": "mkdir -p reports && npx oxlint -c oxlint.json -f checkstyle src > reports/checkstyle.xml",
"types": "tsc --d -emitDeclarationOnly --outDir dist/types",
Expand Down Expand Up @@ -92,6 +110,7 @@
"eventemitter3": "5.0.1",
"geojson": "0.5.0",
"rbush": "4.0.1",
"vanilla-colorful": "0.7.2",
"zundo": "2.3.0",
"zustand": "5.0.7"
},
Expand All @@ -107,7 +126,6 @@
"typedoc-plugin-markdown": "4.9.0",
"typedoc-vitepress-theme": "1.1.2",
"typescript": "5.3.3",
"vanilla-colorful": "0.7.2",
"vite": "6.4.3",
"vite-plugin-dts": "^4.1.0",
"vitest": "3.2.6",
Expand Down
Loading