@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 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
- # Temporary Holding Version
1
+ # @takesix/react
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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;
@@ -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[];
@@ -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;
@@ -0,0 +1,2 @@
1
+ export { TakeSix, type TakeSixProps, type Theme } from './TakeSix.js';
2
+ export type { TakeSixDocument, Block, Link, Size } from '@takesix/core';