@substrate-kit/cli 0.1.2 → 0.2.0
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/.claude-plugin/plugin.json +1 -1
- package/LICENSE +21 -0
- package/README.md +25 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -41
- package/dist/cli.js.map +1 -1
- package/dist/commands/add.d.ts +1 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +51 -109
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/docs.d.ts +4 -2
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +33 -54
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +158 -122
- package/dist/commands/init.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +8 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/get-component.d.ts +1 -1
- package/dist/mcp/tools/get-component.d.ts.map +1 -1
- package/dist/mcp/tools/get-component.js +10 -6
- package/dist/mcp/tools/get-component.js.map +1 -1
- package/dist/mcp/tools/get-docs.d.ts +17 -0
- package/dist/mcp/tools/get-docs.d.ts.map +1 -0
- package/dist/mcp/tools/get-docs.js +23 -0
- package/dist/mcp/tools/get-docs.js.map +1 -0
- package/dist/mcp/tools/install.d.ts +2 -1
- package/dist/mcp/tools/install.d.ts.map +1 -1
- package/dist/mcp/tools/install.js +18 -27
- package/dist/mcp/tools/install.js.map +1 -1
- package/dist/mcp/tools/search.d.ts +1 -1
- package/dist/mcp/tools/search.d.ts.map +1 -1
- package/dist/mcp/tools/search.js +2 -4
- package/dist/mcp/tools/search.js.map +1 -1
- package/dist/{mcp/types.d.ts → registry.d.ts} +6 -1
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +48 -0
- package/dist/registry.js.map +1 -0
- package/dist/utils/agents-file.d.ts +4 -0
- package/dist/utils/agents-file.d.ts.map +1 -0
- package/dist/utils/agents-file.js +36 -0
- package/dist/utils/agents-file.js.map +1 -0
- package/dist/utils/docs.d.ts +7 -0
- package/dist/utils/docs.d.ts.map +1 -0
- package/dist/utils/docs.js +28 -0
- package/dist/utils/docs.js.map +1 -0
- package/dist/utils/shadcn.d.ts +6 -1
- package/dist/utils/shadcn.d.ts.map +1 -1
- package/dist/utils/shadcn.js +25 -12
- package/dist/utils/shadcn.js.map +1 -1
- package/dist/utils/version.d.ts +3 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +8 -0
- package/dist/utils/version.js.map +1 -0
- package/docs/components/action.mdx +30 -0
- package/docs/components/alignment-controls.mdx +30 -0
- package/docs/components/badge.mdx +22 -0
- package/docs/components/breadcrumbs.mdx +25 -0
- package/docs/components/collapsible-pane.mdx +35 -0
- package/docs/components/colour-picker.mdx +66 -0
- package/docs/components/command-palette.mdx +36 -0
- package/docs/components/context-menu.mdx +31 -0
- package/docs/components/controls-timeline.mdx +107 -0
- package/docs/components/controls.mdx +168 -0
- package/docs/components/design-canvas.mdx +51 -0
- package/docs/components/document-tabs.mdx +25 -0
- package/docs/components/easing-curve-editor.mdx +85 -0
- package/docs/components/empty-state.mdx +26 -0
- package/docs/components/gallery.mdx +60 -0
- package/docs/components/history-controls.mdx +47 -0
- package/docs/components/image-picker.mdx +50 -0
- package/docs/components/layer-list.mdx +36 -0
- package/docs/components/node.mdx +85 -0
- package/docs/components/number-input.mdx +44 -0
- package/docs/components/number-slider.mdx +32 -0
- package/docs/components/pane.mdx +39 -0
- package/docs/components/panel.mdx +28 -0
- package/docs/components/popover.mdx +32 -0
- package/docs/components/progress-bar.mdx +24 -0
- package/docs/components/resizer.mdx +24 -0
- package/docs/components/select.mdx +31 -0
- package/docs/components/shortcut-overlay.mdx +43 -0
- package/docs/components/slider.mdx +20 -0
- package/docs/components/spring-editor.mdx +85 -0
- package/docs/components/status-bar.mdx +42 -0
- package/docs/components/surface.mdx +47 -0
- package/docs/components/swatch-palette.mdx +36 -0
- package/docs/components/text-area.mdx +26 -0
- package/docs/components/timeline.mdx +96 -0
- package/docs/components/toast.mdx +42 -0
- package/docs/components/toggle-group.mdx +40 -0
- package/docs/components/toggle.mdx +27 -0
- package/docs/components/toolbar.mdx +24 -0
- package/docs/components/tree-list.mdx +46 -0
- package/docs/components/xy-pad.mdx +49 -0
- package/docs/hooks/use-clipboard.mdx +70 -0
- package/docs/hooks/use-drag-reorder.mdx +60 -0
- package/docs/hooks/use-selection.mdx +68 -0
- package/docs/hooks/use-undoable.mdx +83 -0
- package/docs/hooks/use-viewport.mdx +75 -0
- package/docs/utilities/bounding-box.mdx +76 -0
- package/docs/utilities/keyboard-shortcuts.mdx +64 -0
- package/docs/utilities/snap.mdx +73 -0
- package/package.json +6 -4
- package/registry.json +274 -6
- package/skills/3d-scene/SKILL.md +2 -1
- package/skills/animation/SKILL.md +2 -1
- package/skills/canvas-2d/SKILL.md +67 -5
- package/skills/composing-panels/SKILL.md +1 -4
- package/skills/data-visualisation/SKILL.md +2 -1
- package/skills/image-generation/SKILL.md +2 -1
- package/skills/install-components/SKILL.md +22 -16
- package/skills/scaffold-playground/SKILL.md +39 -19
- package/skills/substrate-canvas/SKILL.md +5 -0
- package/skills/substrate-controls/SKILL.md +57 -2
- package/skills/substrate-interaction/SKILL.md +19 -10
- package/skills/substrate-scaffold/SKILL.md +4 -4
- package/skills/theming/SKILL.md +8 -2
- package/skills/wire-interactions/SKILL.md +6 -3
- package/templates/agents-section.md +41 -0
- package/dist/commands/browse.d.ts +0 -2
- package/dist/commands/browse.d.ts.map +0 -1
- package/dist/commands/browse.js +0 -25
- package/dist/commands/browse.js.map +0 -1
- package/dist/commands/create.d.ts +0 -9
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js +0 -27
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/variables.d.ts +0 -4
- package/dist/commands/variables.d.ts.map +0 -1
- package/dist/commands/variables.js +0 -111
- package/dist/commands/variables.js.map +0 -1
- package/dist/mcp/registry.d.ts +0 -3
- package/dist/mcp/registry.d.ts.map +0 -1
- package/dist/mcp/registry.js +0 -43
- package/dist/mcp/registry.js.map +0 -1
- package/dist/mcp/tools/theme.d.ts +0 -10
- package/dist/mcp/tools/theme.d.ts.map +0 -1
- package/dist/mcp/tools/theme.js +0 -37
- package/dist/mcp/tools/theme.js.map +0 -1
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js +0 -2
- package/dist/mcp/types.js.map +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Controls timeline
|
|
3
|
+
description: Animation clips defined in code and tuned in a timeline editor.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Define animation clips as a plain object, bind their values to your UI, and tune their timing and curves in a timeline. `ControlsTimeline` is the editor: scrub the playhead, drag a clip to move it, drag its edges to resize it, and click it to edit its values with the same rows as [Controls](/docs/components/controls).
|
|
7
|
+
|
|
8
|
+
The clip model and playback come from [DialKit](https://github.com/joshpuckett/dialkit) by Josh Puckett (MIT). Substrate uses its headless timeline and supplies the interface.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/controls-timeline
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
|
|
18
|
+
### Default
|
|
19
|
+
|
|
20
|
+
<ComponentPreview name="controls-timeline-demo" />
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { ControlsTimeline } from "@/components/substrate/controls-timeline"
|
|
26
|
+
import { useControlsTimeline } from "@/hooks/substrate/use-controls-timeline"
|
|
27
|
+
|
|
28
|
+
function Card() {
|
|
29
|
+
const timeline = useControlsTimeline("Card", {
|
|
30
|
+
enter: {
|
|
31
|
+
at: 0,
|
|
32
|
+
duration: 0.6,
|
|
33
|
+
from: { y: 24, opacity: 0 },
|
|
34
|
+
to: { y: 0, opacity: 1 },
|
|
35
|
+
transition: { type: "spring", visualDuration: 0.5, bounce: 0.2 },
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const { y, opacity } = timeline.enter.current
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<div style={{ opacity, transform: `translateY(${y}px)` }}>Card</div>
|
|
44
|
+
<ControlsTimeline />
|
|
45
|
+
</>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`clip.current` holds the clip's values at the playhead, so the element is exactly where the timeline says whether it is playing, paused or being scrubbed.
|
|
51
|
+
|
|
52
|
+
## Clips
|
|
53
|
+
|
|
54
|
+
| Clip | Definition | Editing |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| From–to | `{ at, duration, from, to, transition }` | Drag to move, drag either edge to resize. Physics springs set their own length. |
|
|
57
|
+
| Sequence | `{ at, from, steps: [{ duration, to, transition }] }` | One bar split into steps. Drag a boundary to retime a step. |
|
|
58
|
+
| Property tracks | `{ at, props: { blur: { from, to, duration, delay } } }` | Expand the clip to show a row per property. Drag a row to change its delay. |
|
|
59
|
+
| Group | `{ intro: { title: {...}, badge: {...} } }` | Nested clips show under a collapsible row. |
|
|
60
|
+
|
|
61
|
+
Add `loop: true` to a clip to repeat it until the timeline ends. Faded bars show the repeats. When the whole timeline loops, the ruler shades the part that repeats, starting at `loop.from`.
|
|
62
|
+
|
|
63
|
+
## useControlsTimeline
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const timeline = useControlsTimeline(name, config, options)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Option | Type | Default | Description |
|
|
70
|
+
| --- | --- | --- | --- |
|
|
71
|
+
| `id` | `string` | — | Keeps values across unmounts and shares them between mounts using the same id |
|
|
72
|
+
| `persist` | `boolean \| { key?, storage?, presets? }` | — | Saves values to browser storage |
|
|
73
|
+
| `autoplay` | `boolean` | `true` | Start playing on mount |
|
|
74
|
+
| `loop` | `boolean \| { from: number }` | `false` | Loop at the end, or wrap back to `from` so earlier clips play once |
|
|
75
|
+
|
|
76
|
+
The result has `time`, `playing`, `duration`, `play()`, `pause()`, `replay()` and `seek(seconds)`, plus one entry per clip:
|
|
77
|
+
|
|
78
|
+
| Clip value | Description |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `current` | Values at the playhead |
|
|
81
|
+
| `from`, `to`, `animate` | The clip's end states; `animate` is `to` once the clip has started |
|
|
82
|
+
| `transition`, `css` | The clip's curve, as a Motion transition or CSS duration and timing function |
|
|
83
|
+
| `started`, `active`, `done`, `progress`, `step` | Where the playhead is relative to the clip |
|
|
84
|
+
|
|
85
|
+
## ControlsTimeline
|
|
86
|
+
|
|
87
|
+
| Prop | Type | Default | Description |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| `timeline` | `string` | — | Render only the timeline with this name or id. Renders every timeline when omitted. |
|
|
90
|
+
| `resize` | `"top" \| "bottom" \| false` | `"top"` | Where the handle for resizing the height sits. Use `"top"` for a timeline docked at the bottom of the screen. |
|
|
91
|
+
| `defaultHeight` | `number` | — | Starting height limit in pixels. Unlimited when omitted. |
|
|
92
|
+
| `minHeight` | `number` | `120` | Smallest height the handle allows |
|
|
93
|
+
|
|
94
|
+
Drag the handle, or focus it and use the arrow keys, to limit the height. The rows then scroll under the header and ruler. Double-click the handle to fit the content again.
|
|
95
|
+
|
|
96
|
+
The header has play and pause, replay, the clock, zoom, versions and a copy button. Copy gives an agent the tuned values to apply as the new defaults. Keep the `current` bindings while tuning, then move the values into your production animation.
|
|
97
|
+
|
|
98
|
+
### Zoom
|
|
99
|
+
|
|
100
|
+
Use the zoom buttons, or hold Ctrl or ⌘ and scroll (or pinch on a trackpad) to zoom around the pointer. While zoomed, scroll sideways or hold Shift and scroll to pan, or drag the window in the overview strip below the clips. The view follows the playhead during playback, and **Fit** shows the whole timeline again.
|
|
101
|
+
|
|
102
|
+
## Theme variables
|
|
103
|
+
|
|
104
|
+
| Variable | Fallback | Description |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| `--timeline-clip` | Primary at 18% | Clip bar fill |
|
|
107
|
+
| `--timeline-playhead` | `--color-destructive` | Playhead colour |
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Controls
|
|
3
|
+
description: Config-driven control panels – describe parameters as an object and render them with Substrate controls.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Describe a panel as a plain object and get typed values back. `Controls` renders every registered panel with Substrate's own components, so it sits inside a `Panel` like any hand-built pane.
|
|
7
|
+
|
|
8
|
+
The config format and store come from [DialKit](https://github.com/joshpuckett/dialkit) by Josh Puckett (MIT). Substrate uses its headless store and supplies the interface.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/controls
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
|
|
18
|
+
### Default
|
|
19
|
+
|
|
20
|
+
<ComponentPreview name="controls-demo" />
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { Panel } from "@/components/substrate/panel"
|
|
26
|
+
import { Controls } from "@/components/substrate/controls"
|
|
27
|
+
import { useControls } from "@/hooks/substrate/use-controls"
|
|
28
|
+
|
|
29
|
+
function Editor() {
|
|
30
|
+
const card = useControls("Card", {
|
|
31
|
+
radius: [16, 0, 48],
|
|
32
|
+
colour: "#a78bfa",
|
|
33
|
+
visible: true,
|
|
34
|
+
shadow: { blur: [24, 0, 64] },
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<div style={{ borderRadius: card.radius, background: card.colour }} />
|
|
40
|
+
<Panel>
|
|
41
|
+
<Controls />
|
|
42
|
+
</Panel>
|
|
43
|
+
</>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Keys become labels, and the returned values keep the same nesting as the config.
|
|
49
|
+
|
|
50
|
+
## Config
|
|
51
|
+
|
|
52
|
+
| Control | Definition | Returned value | Rendered with |
|
|
53
|
+
| --- | --- | --- | --- |
|
|
54
|
+
| Slider | `radius: [16, 0, 48]` | `number` | NumberSlider |
|
|
55
|
+
| Slider with step | `radius: [16, 0, 48, 2]` | `number` | NumberSlider |
|
|
56
|
+
| Inferred slider | `scale: 1.2` | `number` | NumberSlider |
|
|
57
|
+
| Toggle | `visible: true` | `boolean` | Toggle |
|
|
58
|
+
| Text | `title: "Hello"` | `string` | TextArea |
|
|
59
|
+
| Colour | `colour: "#a78bfa"` | `string` | ColourPicker |
|
|
60
|
+
| Select | `align: { type: "select", options: ["left", "right"] }` | `string` | Select |
|
|
61
|
+
| Easing | `motion: { type: "easing", duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }` | `{ type, duration, ease }` | EasingCurveEditor |
|
|
62
|
+
| Spring (time) | `motion: { type: "spring", visualDuration: 0.3, bounce: 0.2 }` | `{ type, visualDuration, bounce }` | SpringEditor |
|
|
63
|
+
| Spring (physics) | `motion: { type: "spring", stiffness: 200, damping: 25, mass: 1 }` | `{ type, stiffness, damping, mass }` | SpringEditor |
|
|
64
|
+
| Image | `cover: { type: "image", options: ["/a.jpg", "/b.jpg"] }` | `string` (URL or data URL) | ImagePicker |
|
|
65
|
+
| XY pad | `offset: { type: "pad", x: [0, -50, 50, 1], y: [0, -50, 50, 1] }` | `{ x, y }` | XYPad |
|
|
66
|
+
| Action | `replay: { type: "action" }` | Calls `onAction("replay")` | Button |
|
|
67
|
+
| Folder | `shadow: { blur: [24, 0, 64] }` | Nested values | CollapsiblePane |
|
|
68
|
+
|
|
69
|
+
Slider tuples are `[default, min, max, step?]`. Add `_collapsed: true` inside a folder to start it closed.
|
|
70
|
+
|
|
71
|
+
Easing and spring controls share one row with an Easing / Time / Physics switch, as in DialKit. Switching changes the shape of the returned value, and switching back restores the previous settings. Uploaded images are stored as data URLs, so prefer `options` for large images when persisting values.
|
|
72
|
+
|
|
73
|
+
## useControls
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const values = useControls(name, config, options)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| Option | Type | Description |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| `id` | `string` | Keeps values across unmounts and shares them between mounts using the same id |
|
|
82
|
+
| `persist` | `boolean \| { key?, storage?, presets? }` | Saves values to browser storage |
|
|
83
|
+
| `onAction` | `(path: string) => void` | Called with the path of a clicked action control |
|
|
84
|
+
| `shortcuts` | `Record<string, ShortcutConfig>` | Keyboard shortcuts for sliders and toggles, by control path |
|
|
85
|
+
|
|
86
|
+
### Shortcuts
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
const card = useControls("Card", {
|
|
90
|
+
radius: [16, 0, 48],
|
|
91
|
+
shadow: { blur: [24, 0, 64] },
|
|
92
|
+
visible: true,
|
|
93
|
+
}, {
|
|
94
|
+
shortcuts: {
|
|
95
|
+
radius: { key: "r", mode: "fine" },
|
|
96
|
+
"shadow.blur": { key: "b", interaction: "drag" },
|
|
97
|
+
visible: { key: "v" },
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Hold **R** and scroll to adjust the radius, hold **B** and drag to adjust the blur, and press **V** to toggle visibility. Holding a slider's key also lets the arrow keys step it. The row being adjusted is highlighted, and each row shows its key next to the label.
|
|
103
|
+
|
|
104
|
+
| Field | Type | Default | Description |
|
|
105
|
+
| --- | --- | --- | --- |
|
|
106
|
+
| `key` | `string` | — | The key to hold or press |
|
|
107
|
+
| `modifier` | `"alt" \| "shift" \| "meta"` | — | A modifier that must also be held |
|
|
108
|
+
| `interaction` | `"scroll" \| "drag" \| "move" \| "scroll-only"` | `"scroll"` | How a held slider key adjusts the value. `"scroll-only"` needs no key and claims every scroll, so use it sparingly. |
|
|
109
|
+
| `mode` | `"fine" \| "normal" \| "coarse"` | `"normal"` | Step size: 1% of the range, the slider's step, or 10% of the range |
|
|
110
|
+
|
|
111
|
+
Shortcuts pause while a text field, select or menu has focus, so typing is never captured.
|
|
112
|
+
|
|
113
|
+
### Setting values from code
|
|
114
|
+
|
|
115
|
+
`useControlsController` registers the same panel and also returns setters, for loading a saved state or applying a preset:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
const parameters = useControlsController("Parameters", {
|
|
119
|
+
steps: [30, 1, 100, 1],
|
|
120
|
+
cfgScale: [7, 1, 20, 0.5],
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
parameters.values.steps
|
|
124
|
+
parameters.setValues({ steps: 40 })
|
|
125
|
+
parameters.reset()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Values that belong to a selection
|
|
129
|
+
|
|
130
|
+
A panel owns its values, which suits settings with local state. When the values belong to something selectable, mount a small component per selection with `key`, seed the config from the selected item, and write edits back:
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
function MaterialControls({ object, onChange }: Props) {
|
|
134
|
+
const [config] = useState(
|
|
135
|
+
() =>
|
|
136
|
+
({
|
|
137
|
+
colour: object.colour,
|
|
138
|
+
roughness: [object.roughness, 0, 1, 0.01],
|
|
139
|
+
}) satisfies ControlsConfig,
|
|
140
|
+
)
|
|
141
|
+
const material = useControls("Material", config)
|
|
142
|
+
|
|
143
|
+
const onChangeRef = useRef(onChange)
|
|
144
|
+
onChangeRef.current = onChange
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
onChangeRef.current(material)
|
|
147
|
+
}, [material])
|
|
148
|
+
|
|
149
|
+
return <Controls panel="Material" />
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
<MaterialControls key={selected.id} object={selected} onChange={update} />
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This works when nothing else edits those values while the item is selected. If they also change elsewhere (dragging on a canvas, undo), compose the individual controls by hand so they read straight from your store.
|
|
156
|
+
|
|
157
|
+
## Controls
|
|
158
|
+
|
|
159
|
+
| Prop | Type | Default | Description |
|
|
160
|
+
| --- | --- | --- | --- |
|
|
161
|
+
| `panel` | `string` | — | Render only the panel with this name or id. Renders every panel when omitted. |
|
|
162
|
+
|
|
163
|
+
Each panel header has these actions:
|
|
164
|
+
|
|
165
|
+
- **Keyboard shortcuts** lists the panel's shortcuts. It only appears when the panel has some.
|
|
166
|
+
- **Versions** switches between versions of the panel's values. Edits apply to the version you are on, and **New version** starts a copy of the current values. Version 1 is the values before any version was made. Versions last for the session, or are saved with the values when `persist` is set with `presets: true`.
|
|
167
|
+
- **Copy values** copies the current values as an instruction for an agent to make them the new defaults in the `useControls` call.
|
|
168
|
+
- **Reset** restores the config defaults.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Design Canvas
|
|
3
|
+
description: The main canvas component for rendering and interacting with design elements.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/design-canvas
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { DesignCanvas } from "@/components/substrate/design-canvas"
|
|
16
|
+
|
|
17
|
+
export default function Editor() {
|
|
18
|
+
return (
|
|
19
|
+
<div className="h-screen w-screen">
|
|
20
|
+
<DesignCanvas />
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- HTML5 Canvas 2D rendering with `requestAnimationFrame` loop
|
|
29
|
+
- DPR-aware sizing for sharp rendering on retina displays
|
|
30
|
+
- Dirty-flag optimization — only re-renders when state changes
|
|
31
|
+
- Pointer event handling for create, move, resize, pan, and marquee select
|
|
32
|
+
- Keyboard shortcuts for tool switching, undo/redo, delete, and zoom
|
|
33
|
+
- Cursor changes based on active tool
|
|
34
|
+
|
|
35
|
+
## Theme variables
|
|
36
|
+
|
|
37
|
+
| Variable | Tailwind class | Fallback | Description |
|
|
38
|
+
| --------------------- | ------------------------ | -------------------- | ------------------------ |
|
|
39
|
+
| `--canvas` | `bg-canvas` | `--background` | Canvas background colour |
|
|
40
|
+
| `--canvas-foreground` | `text-canvas-foreground` | `--foreground-muted` | Grid dots and canvas UI |
|
|
41
|
+
|
|
42
|
+
Override in your `globals.css` `:root` / `.dark` blocks to theme the canvas.
|
|
43
|
+
|
|
44
|
+
## Dependencies
|
|
45
|
+
|
|
46
|
+
The canvas component pulls in the full interaction stack:
|
|
47
|
+
|
|
48
|
+
- `tool-store` — active tool state
|
|
49
|
+
- `use-canvas-renderer` — render loop
|
|
50
|
+
- `use-canvas-interaction` — pointer events
|
|
51
|
+
- `use-keyboard-shortcuts` — keyboard handling
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Document tabs
|
|
3
|
+
description: Multi-document tab bar built on Radix Tabs with closeable tabs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/document-tabs
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
### Default
|
|
15
|
+
|
|
16
|
+
<ComponentPreview name="document-tabs-demo" />
|
|
17
|
+
|
|
18
|
+
## Components
|
|
19
|
+
|
|
20
|
+
| Component | Description |
|
|
21
|
+
| -------------------- | ------------------------------------------ |
|
|
22
|
+
| `DocumentTabs` | Root wrapper (Radix Tabs.Root) |
|
|
23
|
+
| `DocumentTabList` | Horizontal scrollable tab bar |
|
|
24
|
+
| `DocumentTab` | Individual tab with optional close button |
|
|
25
|
+
| `DocumentTabContent` | Content panel displayed for the active tab |
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Easing curve editor
|
|
3
|
+
description: Interactive cubic bezier editor with draggable control points and preset buttons.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/easing-curve-editor
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
This also installs the `easing` utility library.
|
|
13
|
+
|
|
14
|
+
## Examples
|
|
15
|
+
|
|
16
|
+
### Default
|
|
17
|
+
|
|
18
|
+
<ComponentPreview name="easing-curve-editor-demo" />
|
|
19
|
+
|
|
20
|
+
### Canvas only
|
|
21
|
+
|
|
22
|
+
<ComponentPreview name="easing-curve-canvas-only" />
|
|
23
|
+
|
|
24
|
+
## Theme variables
|
|
25
|
+
|
|
26
|
+
| Variable | Tailwind class | Fallback | Description |
|
|
27
|
+
| ----------------- | ------------------ | ----------- | ------------------- |
|
|
28
|
+
| `--easing-curve` | `bg-easing-curve` | Text colour | Bezier curve stroke |
|
|
29
|
+
| `--easing-handle` | `bg-easing-handle` | Text colour | Control point fill |
|
|
30
|
+
|
|
31
|
+
## Components
|
|
32
|
+
|
|
33
|
+
| Component | Description |
|
|
34
|
+
| ------------------- | ------------------------------------------------------------------------------ |
|
|
35
|
+
| `EasingCurveEditor` | Combines canvas and presets in a vertical stack |
|
|
36
|
+
| `EasingCurveCanvas` | Canvas-rendered bezier curve with draggable control points via pointer capture |
|
|
37
|
+
| `EasingPresets` | Row of buttons for common easing presets (linear, ease-in, ease-out, etc.) |
|
|
38
|
+
|
|
39
|
+
## Props
|
|
40
|
+
|
|
41
|
+
### EasingCurveEditor
|
|
42
|
+
|
|
43
|
+
| Prop | Type | Default | Description |
|
|
44
|
+
| ---------- | ------------------------------- | ------- | ------------------------- |
|
|
45
|
+
| `value` | `CubicBezier` | — | Current bezier value |
|
|
46
|
+
| `onChange` | `(bezier: CubicBezier) => void` | — | Called when value changes |
|
|
47
|
+
|
|
48
|
+
### EasingCurveCanvas
|
|
49
|
+
|
|
50
|
+
| Prop | Type | Default | Description |
|
|
51
|
+
| ---------- | ------------------------------- | ------- | ------------------------- |
|
|
52
|
+
| `value` | `CubicBezier` | — | Current bezier value |
|
|
53
|
+
| `onChange` | `(bezier: CubicBezier) => void` | — | Called when value changes |
|
|
54
|
+
| `width` | `number` | `200` | Canvas width in pixels |
|
|
55
|
+
| `height` | `number` | `160` | Canvas height in pixels |
|
|
56
|
+
|
|
57
|
+
### EasingPresets
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Default | Description |
|
|
60
|
+
| ---------- | ------------------------------- | ------- | ----------------------------- |
|
|
61
|
+
| `value` | `CubicBezier` | — | Current bezier for highlight |
|
|
62
|
+
| `onChange` | `(bezier: CubicBezier) => void` | — | Called when preset is clicked |
|
|
63
|
+
|
|
64
|
+
## Easing utility
|
|
65
|
+
|
|
66
|
+
The `easing` library provides types and helpers:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import {
|
|
70
|
+
CubicBezier,
|
|
71
|
+
EASING_PRESETS,
|
|
72
|
+
evaluateCubicBezier,
|
|
73
|
+
cubicBezierToString,
|
|
74
|
+
parseCubicBezier,
|
|
75
|
+
} from "@/lib/substrate/easing"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Export | Description |
|
|
79
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `CubicBezier` | Type: `{ x1, y1, x2, y2 }` |
|
|
81
|
+
| `EASING_PRESETS` | 7 named presets (linear, ease-in, ease-out, ease-in-out, ease-in-cubic, ease-out-cubic, ease-in-out-cubic) |
|
|
82
|
+
| `evaluateCubicBezier` | Evaluate curve at time `t` using Newton's method (8 iterations) |
|
|
83
|
+
| `cubicBezierToString` | Convert to CSS `cubic-bezier(x1, y1, x2, y2)` string |
|
|
84
|
+
| `parseCubicBezier` | Parse a CSS cubic-bezier string back to object |
|
|
85
|
+
| `isCubicBezierEqual` | Compare two bezier values for equality |
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Empty state
|
|
3
|
+
description: Placeholder for empty panels, lists, and canvases. Icon, title, description, and optional action.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/empty-state
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
### Default
|
|
15
|
+
|
|
16
|
+
<ComponentPreview name="empty-state-demo" />
|
|
17
|
+
|
|
18
|
+
## Components
|
|
19
|
+
|
|
20
|
+
| Component | Description |
|
|
21
|
+
| ----------------------- | -------------------------------------------- |
|
|
22
|
+
| `EmptyState` | Centred flex container with vertical spacing |
|
|
23
|
+
| `EmptyStateIcon` | Rounded icon container |
|
|
24
|
+
| `EmptyStateTitle` | Short heading |
|
|
25
|
+
| `EmptyStateDescription` | Descriptive text, max-width constrained |
|
|
26
|
+
| `EmptyStateAction` | Primary action button |
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Gallery
|
|
3
|
+
description: Responsive thumbnail grid with selection, hover actions, and empty state.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/gallery
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
### Default
|
|
15
|
+
|
|
16
|
+
<ComponentPreview name="gallery-demo" />
|
|
17
|
+
|
|
18
|
+
### Empty state
|
|
19
|
+
|
|
20
|
+
<ComponentPreview name="gallery-empty" />
|
|
21
|
+
|
|
22
|
+
## Theme variables
|
|
23
|
+
|
|
24
|
+
| Variable | Tailwind class | Fallback | Description |
|
|
25
|
+
| --------------------- | ------------------------ | ----------- | --------------------- |
|
|
26
|
+
| `--gallery-selection` | `ring-gallery-selection` | `--primary` | Selection ring colour |
|
|
27
|
+
|
|
28
|
+
## Components
|
|
29
|
+
|
|
30
|
+
| Component | Description |
|
|
31
|
+
| --------------- | ------------------------------------------------------------------------ |
|
|
32
|
+
| `Gallery` | CSS grid container with configurable columns and gap |
|
|
33
|
+
| `GalleryItem` | Aspect-square cell with selection ring, hover overlay, and click handler |
|
|
34
|
+
| `GalleryImage` | `<img>` with `object-cover` filling the item |
|
|
35
|
+
| `GalleryAction` | Button overlaid on hover in the top-right corner of an item |
|
|
36
|
+
| `GalleryEmpty` | Full-width placeholder spanning all columns |
|
|
37
|
+
|
|
38
|
+
## Props
|
|
39
|
+
|
|
40
|
+
### Gallery
|
|
41
|
+
|
|
42
|
+
| Prop | Type | Default | Description |
|
|
43
|
+
| --------- | -------- | ------- | ------------------------------------ |
|
|
44
|
+
| `columns` | `number` | `3` | Number of grid columns |
|
|
45
|
+
| `gap` | `number` | `2` | Gap between items (in 0.25rem units) |
|
|
46
|
+
|
|
47
|
+
### GalleryItem
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Description |
|
|
50
|
+
| ---------- | ------------ | ------- | -------------------- |
|
|
51
|
+
| `selected` | `boolean` | `false` | Shows selection ring |
|
|
52
|
+
| `onSelect` | `() => void` | — | Click handler |
|
|
53
|
+
|
|
54
|
+
### GalleryImage
|
|
55
|
+
|
|
56
|
+
Extends `React.ComponentProps<"img">`.
|
|
57
|
+
|
|
58
|
+
### GalleryAction
|
|
59
|
+
|
|
60
|
+
Extends `React.ComponentProps<"button">`.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: History controls
|
|
3
|
+
description: Undo and redo buttons that read from the document store. Position-agnostic — use inside a toolbar, panel header, or floating independently.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/history-controls
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
### Default
|
|
15
|
+
|
|
16
|
+
<ComponentPreview name="history-controls-demo" />
|
|
17
|
+
|
|
18
|
+
### Inside a toolbar
|
|
19
|
+
|
|
20
|
+
<ComponentPreview name="history-controls-toolbar" />
|
|
21
|
+
|
|
22
|
+
## Components
|
|
23
|
+
|
|
24
|
+
| Component | Description |
|
|
25
|
+
| ----------------- | -------------------------------------------------- |
|
|
26
|
+
| `HistoryControls` | Inline flex container with undo and redo buttons |
|
|
27
|
+
| `HistoryButton` | Individual icon button with disabled state styling |
|
|
28
|
+
|
|
29
|
+
## Props
|
|
30
|
+
|
|
31
|
+
### HistoryControls
|
|
32
|
+
|
|
33
|
+
| Prop | Type | Default | Description |
|
|
34
|
+
| ------ | -------------- | ------- | -------------------- |
|
|
35
|
+
| `size` | `"sm" \| "md"` | `"md"` | Button and icon size |
|
|
36
|
+
|
|
37
|
+
Extends `React.ComponentProps<"div">`.
|
|
38
|
+
|
|
39
|
+
## Behaviour
|
|
40
|
+
|
|
41
|
+
The component reads directly from the document store:
|
|
42
|
+
|
|
43
|
+
- **Undo** is enabled when `past.length > 0`
|
|
44
|
+
- **Redo** is enabled when `future.length > 0`
|
|
45
|
+
- Disabled buttons drop to 30% opacity and ignore pointer events
|
|
46
|
+
|
|
47
|
+
Keyboard shortcuts (`Cmd+Z` / `Cmd+Shift+Z`) are handled separately in `use-keyboard-shortcuts` — this component only provides the visual controls.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Image picker
|
|
3
|
+
description: Thumbnail grid for choosing an image from a list, with upload and drag-and-drop.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/image-picker
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
### Default
|
|
15
|
+
|
|
16
|
+
<ComponentPreview name="image-picker-demo" />
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
const [image, setImage] = useState("/textures/paper.jpg")
|
|
22
|
+
|
|
23
|
+
<ImagePicker
|
|
24
|
+
value={image}
|
|
25
|
+
onChange={setImage}
|
|
26
|
+
options={[
|
|
27
|
+
{ value: "/textures/paper.jpg", label: "Paper" },
|
|
28
|
+
"/textures/grain.png",
|
|
29
|
+
]}
|
|
30
|
+
/>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Plain string options are labelled from their file name. Uploaded and dropped files are read as data URLs and added to the grid for as long as the picker is mounted. A value that isn't in the list, such as one restored from storage, still shows as selected.
|
|
34
|
+
|
|
35
|
+
## Theme variables
|
|
36
|
+
|
|
37
|
+
| Variable | Tailwind class | Fallback | Description |
|
|
38
|
+
| ------------------------- | ---------------------------- | ------------- | ----------------------- |
|
|
39
|
+
| `--image-picker-selection` | `bg-image-picker-selection` | Primary colour | Selected image ring |
|
|
40
|
+
|
|
41
|
+
## Props
|
|
42
|
+
|
|
43
|
+
| Prop | Type | Default | Description |
|
|
44
|
+
| ---------- | ---------------------------------------------- | ------- | --------------------------------------------- |
|
|
45
|
+
| `value` | `string` | — | Selected image URL, or `""` for none |
|
|
46
|
+
| `onChange` | `(value: string) => void` | — | Called with the chosen or uploaded image |
|
|
47
|
+
| `options` | `(string \| { value: string, label: string })[]` | `[]` | Images to choose from |
|
|
48
|
+
| `upload` | `boolean` | `true` | Show the upload tile and accept dropped files |
|
|
49
|
+
| `columns` | `number` | `4` | Thumbnails per row |
|
|
50
|
+
| `disabled` | `boolean` | `false` | Disables choosing and uploading |
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Layer list
|
|
3
|
+
description: Reorderable layer panel with visibility, lock toggles, and inline rename.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx shadcn@latest add https://substrate.georgedrury.co.uk/r/layer-list
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
### Default
|
|
15
|
+
|
|
16
|
+
<ComponentPreview name="layer-list-demo" />
|
|
17
|
+
|
|
18
|
+
## Interaction
|
|
19
|
+
|
|
20
|
+
- **Click** a row to select it.
|
|
21
|
+
- **Double-click** the name to rename inline. Press Enter to confirm or Escape to cancel.
|
|
22
|
+
- **Visibility toggle** — hides/shows the layer.
|
|
23
|
+
- **Lock toggle** — locks the layer to prevent selection or editing.
|
|
24
|
+
|
|
25
|
+
## Components
|
|
26
|
+
|
|
27
|
+
| Component | Description |
|
|
28
|
+
| ----------------------- | ------------------------------------------------ |
|
|
29
|
+
| `LayerList` | Vertical flex container for layer items |
|
|
30
|
+
| `LayerItem` | Row with selection, hover, and depth indentation |
|
|
31
|
+
| `LayerIcon` | Colour dot or icon thumbnail |
|
|
32
|
+
| `LayerName` | Truncated name with double-click-to-rename |
|
|
33
|
+
| `LayerActions` | Right-aligned action slot, visible on hover |
|
|
34
|
+
| `LayerToggle` | Generic toggle button for layer actions |
|
|
35
|
+
| `LayerVisibilityToggle` | Convenience eye/eye-off toggle |
|
|
36
|
+
| `LayerLockToggle` | Convenience lock/unlock toggle |
|