@takesix/react 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +143 -2
- package/dist/BlockCard.d.ts +41 -0
- package/dist/DetailsPanel.d.ts +11 -0
- package/dist/LinkPopover.d.ts +17 -0
- package/dist/TakeSix.d.ts +18 -0
- package/dist/Viewer.d.ts +30 -0
- package/dist/geometry.d.ts +50 -0
- package/dist/icons.d.ts +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1286 -0
- package/dist/markdown.d.ts +7 -0
- package/dist/style.css +2 -0
- package/package.json +66 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vvapnik
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,144 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @takesix/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React viewer and editor for [takesix](https://github.com/vvapnik/takesix-core)
|
|
4
|
+
diagrams. One screen shows at most **6 blocks**; click a block to zoom into
|
|
5
|
+
its children. Links between blocks are drawn on every level, including links
|
|
6
|
+
between blocks nested deep inside different parents.
|
|
7
|
+
|
|
8
|
+
Use it to show an AI agent's answer as a diagram rather than a wall of text:
|
|
9
|
+
render the takesix JSON the agent wrote, let the reader zoom in, refine it in
|
|
10
|
+
the editor.
|
|
11
|
+
|
|
12
|
+
The document format and all editing logic live in
|
|
13
|
+
[`@takesix/core`](https://github.com/vvapnik/takesix-core); this package only
|
|
14
|
+
renders and edits.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @takesix/react @takesix/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Peer dependencies: `react` and `react-dom` 18 or newer.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { useState } from 'react'
|
|
28
|
+
import { parseDocument, type TakeSixDocument } from '@takesix/core'
|
|
29
|
+
import { TakeSix } from '@takesix/react'
|
|
30
|
+
import '@takesix/react/style.css'
|
|
31
|
+
|
|
32
|
+
export function Diagram({ json }: { json: string }) {
|
|
33
|
+
const result = parseDocument(json)
|
|
34
|
+
if (!result.ok) return <p>Invalid diagram</p>
|
|
35
|
+
return <Editor initial={result.document} />
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function Editor({ initial }: { initial: TakeSixDocument }) {
|
|
39
|
+
const [doc, setDoc] = useState(initial)
|
|
40
|
+
const [editing, setEditing] = useState(false)
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<button onClick={() => setEditing(e => !e)}>{editing ? 'Done' : 'Edit'}</button>
|
|
44
|
+
<div style={{ height: 600 }}>
|
|
45
|
+
<TakeSix document={doc} onChange={setDoc} editing={editing} />
|
|
46
|
+
</div>
|
|
47
|
+
</>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`TakeSix` fills its container, so give the container a height. It is only the
|
|
53
|
+
view: the Edit toggle belongs to your app (a toolbar, a menu).
|
|
54
|
+
|
|
55
|
+
### Props
|
|
56
|
+
|
|
57
|
+
| Prop | Type | Meaning |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `document` | `TakeSixDocument` | The diagram. Validate untrusted input with `parseDocument` first. |
|
|
60
|
+
| `onChange` | `(doc) => void` | Receives the edited document. Without it the diagram is read-only. |
|
|
61
|
+
| `editing` | `boolean` | Show the edit controls. Ignored without `onChange`. |
|
|
62
|
+
| `theme` | `'auto' \| 'light' \| 'dark'` | Colour theme. `auto` (default) follows the system setting. |
|
|
63
|
+
| `className` | `string` | Extra class for the root element. |
|
|
64
|
+
|
|
65
|
+
### Theming
|
|
66
|
+
|
|
67
|
+
All colours are CSS variables on the root element (`--ts-bg`, `--ts-surface`,
|
|
68
|
+
`--ts-text`, `--ts-accent`, …; see `src/TakeSix.module.css`). Pick a built-in
|
|
69
|
+
theme with the `theme` prop, or override variables through `className`:
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
.myDiagram {
|
|
73
|
+
--ts-accent: #0f9d58;
|
|
74
|
+
--ts-bg: transparent;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### What is shown
|
|
79
|
+
|
|
80
|
+
- **One level per screen**, with a breadcrumb. There is no header for the
|
|
81
|
+
opened block: if a level needs a title or summary, make it the first block.
|
|
82
|
+
- **Cards** show icon, title and description, and "N more inside" when the
|
|
83
|
+
block has children (direct children only). Click anywhere on such a card to
|
|
84
|
+
open it; text stays selectable.
|
|
85
|
+
|
|
86
|
+
- **Arrows**: a link between nested blocks is drawn between their ancestors on
|
|
87
|
+
the current screen. Several links between the same pair of blocks merge into
|
|
88
|
+
one arrow labelled with their labels, or with the count when there are more
|
|
89
|
+
than two. An arrow that stands for nested links is dashed.
|
|
90
|
+
- **Directions**: `forward` has one arrowhead, `both` two, `none` none.
|
|
91
|
+
- **Details**: a block with `details` gets a details button (on its card, and
|
|
92
|
+
in the header once opened) that opens a side panel. `details` is Markdown,
|
|
93
|
+
a safe subset rendered without HTML: `#`–`###` headings, paragraphs, `-`/`1.`
|
|
94
|
+
lists, fenced code, `**bold**`, `*italic*`, `` `code` ``, and links with
|
|
95
|
+
`http(s)`/`mailto` URLs (other schemes are shown as plain text).
|
|
96
|
+
- **Sizes**: a level is one flow of blocks on a 6-column grid. Each block
|
|
97
|
+
takes a third (`S`), a half (`M`, default), two thirds (`L`) or the full
|
|
98
|
+
width (`F`); blocks wrap to the next row when they do not fit. Six `F`
|
|
99
|
+
blocks make a list, three `S` a row of tiles. A full-width card is laid out
|
|
100
|
+
as a row.
|
|
101
|
+
|
|
102
|
+
### Editing
|
|
103
|
+
|
|
104
|
+
In edit mode you can:
|
|
105
|
+
|
|
106
|
+
- rename blocks and edit descriptions in place;
|
|
107
|
+
- add and delete blocks; a level stops offering "add" at 6 blocks;
|
|
108
|
+
- group blocks: "Group blocks" in the toolbar, select two or more, Group. They
|
|
109
|
+
move into a new block ("New group") where the first of them was, which frees
|
|
110
|
+
room on a full level;
|
|
111
|
+
- reorder blocks by dragging the dotted handle on their left;
|
|
112
|
+
- link blocks: press the arrow button on the source, then click the target.
|
|
113
|
+
The target may be on any level: while linking, open blocks ("N more inside")
|
|
114
|
+
or go back up to find it. A block's own ancestors and descendants cannot be
|
|
115
|
+
targets. Clicking a linked target removes the link; Esc or Cancel stops;
|
|
116
|
+
- edit links: click an arrow or its label to change the label and direction
|
|
117
|
+
(one way, both ways, none), reverse or delete it. A merged arrow lists every
|
|
118
|
+
link it stands for, with their real endpoints;
|
|
119
|
+
- write details: the details button opens the panel with a Markdown editor
|
|
120
|
+
and a live preview;
|
|
121
|
+
- resize blocks: the size button (⅓ ½ ⅔ 1) cycles the size, or drag the
|
|
122
|
+
card's right edge, which snaps to the four sizes.
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
Node 24 LTS (see `.nvmrc`; with nvm: `nvm use`). `@takesix/core` comes from
|
|
127
|
+
npm; to try unreleased core changes, run `npm link` in a `takesix-core` checkout
|
|
128
|
+
and `npm link @takesix/core` here.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm install
|
|
132
|
+
npm run dev # demo app from examples/
|
|
133
|
+
npm test
|
|
134
|
+
npm run build
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## AI-assisted development
|
|
138
|
+
|
|
139
|
+
This project is developed with the help of [Claude Code](https://claude.com/claude-code)
|
|
140
|
+
(Anthropic). Commits are authored and reviewed by the maintainer.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Block, type Size } from '@takesix/core';
|
|
2
|
+
export interface CardRect {
|
|
3
|
+
left: number;
|
|
4
|
+
top: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
/** Where a dragged block would land relative to this card. */
|
|
9
|
+
export type DropPosition = 'before' | 'after';
|
|
10
|
+
/** Grid columns (of 6) each size spans. */
|
|
11
|
+
export declare const SIZE_SPAN: Record<Size, number>;
|
|
12
|
+
interface Props {
|
|
13
|
+
block: Block;
|
|
14
|
+
editing: boolean;
|
|
15
|
+
onRename: (id: string, title: string) => void;
|
|
16
|
+
onDescribe: (id: string, description: string) => void;
|
|
17
|
+
onDelete: (id: string) => void;
|
|
18
|
+
onAddChild: (block: Block, cardRect: CardRect) => void;
|
|
19
|
+
onDrillIn: (block: Block, cardRect: CardRect) => void;
|
|
20
|
+
connectingFrom: string | null;
|
|
21
|
+
/** While linking: this block may be the target (not the source, its ancestor or descendant). */
|
|
22
|
+
canLinkTo: boolean;
|
|
23
|
+
isConnected: boolean;
|
|
24
|
+
onStartConnect: (id: string) => void;
|
|
25
|
+
onConnectTarget: (id: string) => void;
|
|
26
|
+
onRegisterEl: (id: string, el: HTMLDivElement | null) => void;
|
|
27
|
+
/** Drag and drop to reorder, edit mode only. */
|
|
28
|
+
isDragged: boolean;
|
|
29
|
+
dropPosition: DropPosition | null;
|
|
30
|
+
onDragStartBlock: (id: string) => void;
|
|
31
|
+
onDragOverBlock: (id: string, position: DropPosition) => void;
|
|
32
|
+
onDragEndBlock: () => void;
|
|
33
|
+
onShowDetails: (id: string) => void;
|
|
34
|
+
/** Choosing blocks to group: a click selects instead of opening. */
|
|
35
|
+
selectable: boolean;
|
|
36
|
+
selected: boolean;
|
|
37
|
+
onToggleSelect: (id: string) => void;
|
|
38
|
+
onResize: (id: string, size: Size) => void;
|
|
39
|
+
}
|
|
40
|
+
export declare function BlockCard({ block, editing, onRename, onDescribe, onDelete, onAddChild, onDrillIn, connectingFrom, canLinkTo, isConnected, onStartConnect, onConnectTarget, onRegisterEl, isDragged, dropPosition, onDragStartBlock, onDragOverBlock, onDragEndBlock, onShowDetails, selectable, selected, onToggleSelect, onResize, }: Props): import("react").JSX.Element;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Block } from '@takesix/core';
|
|
2
|
+
interface Props {
|
|
3
|
+
block: Block;
|
|
4
|
+
editing: boolean;
|
|
5
|
+
/** Saves the Markdown source; an empty string removes `details`. */
|
|
6
|
+
onChange: (details: string) => void;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}
|
|
9
|
+
/** Side panel with a block's long description (`details`, Markdown). */
|
|
10
|
+
export declare function DetailsPanel({ block, editing, onChange, onClose }: Props): import("react").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Link } from '@takesix/core';
|
|
2
|
+
/** Changes to one link. A key set to `undefined` removes that field. */
|
|
3
|
+
export type LinkEdit = Partial<Pick<Link, 'from' | 'to' | 'label' | 'direction'>>;
|
|
4
|
+
interface Props {
|
|
5
|
+
/** The document links one arrow stands for: usually one, several when merged. */
|
|
6
|
+
links: Link[];
|
|
7
|
+
/** Where the arrow's middle is, relative to the arrow layer. */
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
titleOf: (id: string) => string;
|
|
11
|
+
onUpdate: (link: Link, changes: LinkEdit) => void;
|
|
12
|
+
onRemove: (link: Link) => void;
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
}
|
|
15
|
+
/** Edits the links behind one arrow: label, direction, reverse, delete. */
|
|
16
|
+
export declare function LinkPopover({ links, x, y, titleOf, onUpdate, onRemove, onClose }: Props): import("react").JSX.Element;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type TakeSixDocument } from '@takesix/core';
|
|
2
|
+
export interface TakeSixProps {
|
|
3
|
+
/** The diagram to show. Expected to be valid: run `parseDocument` from `@takesix/core` on untrusted input. */
|
|
4
|
+
document: TakeSixDocument;
|
|
5
|
+
/** Called with the edited document. Without it the diagram stays read-only. */
|
|
6
|
+
onChange?: (document: TakeSixDocument) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Show the edit controls. The toggle lives in the host app (e.g. its toolbar),
|
|
9
|
+
* so the component itself is only the view. Ignored without `onChange`.
|
|
10
|
+
*/
|
|
11
|
+
editing?: boolean;
|
|
12
|
+
/** Colour theme. `auto` (default) follows the system setting. */
|
|
13
|
+
theme?: Theme;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
export type Theme = 'light' | 'dark' | 'auto';
|
|
17
|
+
/** Viewer and editor for a takesix document: one level on screen, click a block to open it. */
|
|
18
|
+
export declare function TakeSix({ document: doc, onChange, editing: editingProp, theme, className }: TakeSixProps): import("react").JSX.Element;
|
package/dist/Viewer.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Size, type Link, type TakeSixDocument } from '@takesix/core';
|
|
2
|
+
import { type LinkEdit } from './LinkPopover.js';
|
|
3
|
+
/** Edits the viewer asks for. All of them are optional: without one, the matching control is hidden. */
|
|
4
|
+
export interface ViewerActions {
|
|
5
|
+
rename: (id: string, title: string) => void;
|
|
6
|
+
describe: (id: string, description: string) => void;
|
|
7
|
+
remove: (id: string) => void;
|
|
8
|
+
/** Adds an empty block under `parentId` (`null` = top level). */
|
|
9
|
+
add: (parentId: string | null) => void;
|
|
10
|
+
/** Sets a block's width on its level. */
|
|
11
|
+
setSize: (id: string, size: Size) => void;
|
|
12
|
+
/** Links `fromId` → `toId`, or removes that link when it exists. */
|
|
13
|
+
toggleLink: (fromId: string, toId: string) => void;
|
|
14
|
+
/** Moves a block to position `index` among the children of `parentId`. */
|
|
15
|
+
move: (id: string, parentId: string | null, index: number) => void;
|
|
16
|
+
/** Edits one link, found by identity (links in files may have no id). */
|
|
17
|
+
updateLink: (link: Link, changes: LinkEdit) => void;
|
|
18
|
+
removeLink: (link: Link) => void;
|
|
19
|
+
/** Saves a block's Markdown details; an empty string removes them. */
|
|
20
|
+
setDetails: (id: string, details: string) => void;
|
|
21
|
+
/** Wraps sibling blocks into a new parent block, freeing room on the level. */
|
|
22
|
+
group: (ids: string[]) => void;
|
|
23
|
+
}
|
|
24
|
+
interface Props {
|
|
25
|
+
document: TakeSixDocument;
|
|
26
|
+
editing: boolean;
|
|
27
|
+
actions: ViewerActions;
|
|
28
|
+
}
|
|
29
|
+
export declare function Viewer({ document: doc, editing, actions }: Props): import("react").JSX.Element;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface Rect {
|
|
2
|
+
left: number;
|
|
3
|
+
top: number;
|
|
4
|
+
right: number;
|
|
5
|
+
bottom: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
export interface Point {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
export type Edge = 'left' | 'right' | 'top' | 'bottom';
|
|
14
|
+
export interface ArrowRequest {
|
|
15
|
+
key: string;
|
|
16
|
+
fromId: string;
|
|
17
|
+
toId: string;
|
|
18
|
+
/** An arrowhead is drawn at this end: the path stops at the head's base, short of the card. */
|
|
19
|
+
headAtStart?: boolean;
|
|
20
|
+
headAtEnd?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Space between an arrowhead's tip and the card it points at. */
|
|
23
|
+
export declare const HEAD_GAP = 4;
|
|
24
|
+
/** Length of the arrowhead. The path ends at its base and the head is drawn
|
|
25
|
+
beyond it, so the line never shows through the tip. */
|
|
26
|
+
export declare const HEAD_LENGTH = 10;
|
|
27
|
+
export interface ArrowPath {
|
|
28
|
+
key: string;
|
|
29
|
+
/** SVG path data, relative to the container. */
|
|
30
|
+
d: string;
|
|
31
|
+
/** Middle of the curve, for the label. */
|
|
32
|
+
mid: Point;
|
|
33
|
+
/** Runs from one card's top/bottom edge to the other's: put the label beside it, not on it. */
|
|
34
|
+
vertical: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function determineEdges(fromRect: Rect, toRect: Rect, obstacleRects: Rect[]): {
|
|
37
|
+
exitEdge: Edge;
|
|
38
|
+
entryEdge: Edge;
|
|
39
|
+
};
|
|
40
|
+
export declare function edgePoint(rect: Rect, edge: Edge, idx: number, total: number, origin: Point): Point;
|
|
41
|
+
/** Cubic Bézier control points from `p1` (on `exitEdge`) to `p2` (on `entryEdge`). */
|
|
42
|
+
export declare function controlPoints(fromRect: Rect, toRect: Rect, origin: Point, exitEdge: Edge, entryEdge: Edge, p1: Point, p2: Point): [Point, Point];
|
|
43
|
+
/** Point of a cubic Bézier at t = 0.5. */
|
|
44
|
+
export declare function bezierMid(p0: Point, c1: Point, c2: Point, p3: Point): Point;
|
|
45
|
+
/**
|
|
46
|
+
* Routes all arrows of one screen. Exits and entries on the same edge of the
|
|
47
|
+
* same card share one pool of slots, so arrows never start or end on top of
|
|
48
|
+
* each other. Arrows whose cards have no rect are skipped.
|
|
49
|
+
*/
|
|
50
|
+
export declare function routeArrows(requests: ArrowRequest[], rects: Map<string, Rect>, container: Rect): ArrowPath[];
|
package/dist/icons.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function PlusIcon({ size }: {
|
|
2
|
+
size?: number;
|
|
3
|
+
}): import("react").JSX.Element;
|
|
4
|
+
export declare function CrossIcon(): import("react").JSX.Element;
|
|
5
|
+
export declare function ArrowRightIcon(): import("react").JSX.Element;
|
|
6
|
+
export declare function DetailsIcon(): import("react").JSX.Element;
|
package/dist/index.d.ts
ADDED