@rogieking/figui3 8.9.44 → 8.9.45
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/.cursor/skills/fig-editor/SKILL.md +11 -138
- package/.cursor/skills/fig-editor/components.md +134 -0
- package/.cursor/skills/fig-editor/reference.md +2 -3
- package/.cursor/skills/fig-lab/SKILL.md +9 -109
- package/.cursor/skills/fig-lab/components.md +371 -0
- package/.cursor/skills/fig-lab/reference.md +5 -3
- package/.cursor/skills/figui3/SKILL.md +23 -132
- package/.cursor/skills/figui3/components.md +643 -0
- package/.cursor/skills/figui3/react.md +263 -0
- package/.cursor/skills/figui3/reference.md +3 -4
- package/.cursor/skills/propkit/SKILL.md +25 -35
- package/package.json +1 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# React + FigUI3
|
|
2
|
+
|
|
3
|
+
FigUI3 is web components. Use the tags in JSX. There is no React wrapper package.
|
|
4
|
+
|
|
5
|
+
Shared by `figui3`, `fig-editor`, and `fig-lab`. Per-tag recipes: [components.md](components.md).
|
|
6
|
+
|
|
7
|
+
## Bootstrap
|
|
8
|
+
|
|
9
|
+
Register CSS + JS before the first render. Dynamic `import()` keeps bundlers from tree-shaking registration.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { createRoot } from "react-dom/client";
|
|
13
|
+
import "@rogieking/figui3/fig.css";
|
|
14
|
+
|
|
15
|
+
const bootstrap = async () => {
|
|
16
|
+
await import("@rogieking/figui3/fig.js");
|
|
17
|
+
createRoot(document.getElementById("app")!).render(<App />);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
bootstrap();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Add editor and/or lab the same way, before render:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import "@rogieking/figui3/fig-editor.css";
|
|
27
|
+
await import("@rogieking/figui3/fig-editor.js");
|
|
28
|
+
|
|
29
|
+
import "@rogieking/figui3/fig-lab.css";
|
|
30
|
+
await import("@rogieking/figui3/fig-lab.js");
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
SSR (Next/Remix/Astro): import the JS only on the client.
|
|
34
|
+
|
|
35
|
+
## JSX types
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import "react";
|
|
39
|
+
|
|
40
|
+
type FigAttrs = React.DetailedHTMLProps<
|
|
41
|
+
React.HTMLAttributes<HTMLElement>,
|
|
42
|
+
HTMLElement
|
|
43
|
+
> & { [key: string]: unknown };
|
|
44
|
+
|
|
45
|
+
type FigTag =
|
|
46
|
+
| "fig-3d-rotate"
|
|
47
|
+
| "fig-ai-context"
|
|
48
|
+
| "fig-ai-prompt"
|
|
49
|
+
| "fig-attachment"
|
|
50
|
+
| "fig-attachments"
|
|
51
|
+
| "fig-avatar"
|
|
52
|
+
| "fig-button"
|
|
53
|
+
| "fig-button-combo"
|
|
54
|
+
| "fig-canvas-control"
|
|
55
|
+
| "fig-card"
|
|
56
|
+
| "fig-chat-message"
|
|
57
|
+
| "fig-checkbox"
|
|
58
|
+
| "fig-chit"
|
|
59
|
+
| "fig-choice"
|
|
60
|
+
| "fig-chooser"
|
|
61
|
+
| "fig-color-tip"
|
|
62
|
+
| "fig-combo-input"
|
|
63
|
+
| "fig-content"
|
|
64
|
+
| "fig-dropdown"
|
|
65
|
+
| "fig-easing-curve"
|
|
66
|
+
| "fig-field"
|
|
67
|
+
| "fig-fill-picker"
|
|
68
|
+
| "fig-footer"
|
|
69
|
+
| "fig-group"
|
|
70
|
+
| "fig-handle"
|
|
71
|
+
| "fig-header"
|
|
72
|
+
| "fig-icon"
|
|
73
|
+
| "fig-image"
|
|
74
|
+
| "fig-input-angle"
|
|
75
|
+
| "fig-input-color"
|
|
76
|
+
| "fig-input-combo"
|
|
77
|
+
| "fig-input-file"
|
|
78
|
+
| "fig-input-fill"
|
|
79
|
+
| "fig-input-gradient"
|
|
80
|
+
| "fig-input-number"
|
|
81
|
+
| "fig-input-palette"
|
|
82
|
+
| "fig-input-text"
|
|
83
|
+
| "fig-input-wheel"
|
|
84
|
+
| "fig-interpolation-swatch"
|
|
85
|
+
| "fig-joystick"
|
|
86
|
+
| "fig-layer"
|
|
87
|
+
| "fig-media"
|
|
88
|
+
| "fig-media-controls"
|
|
89
|
+
| "fig-menu"
|
|
90
|
+
| "fig-menu-item"
|
|
91
|
+
| "fig-menu-separator"
|
|
92
|
+
| "fig-options"
|
|
93
|
+
| "fig-origin-grid"
|
|
94
|
+
| "fig-preview"
|
|
95
|
+
| "fig-radio"
|
|
96
|
+
| "fig-reorder"
|
|
97
|
+
| "fig-segment"
|
|
98
|
+
| "fig-segmented-control"
|
|
99
|
+
| "fig-select"
|
|
100
|
+
| "fig-select-option"
|
|
101
|
+
| "fig-select-options"
|
|
102
|
+
| "fig-separator"
|
|
103
|
+
| "fig-shimmer"
|
|
104
|
+
| "fig-skeleton"
|
|
105
|
+
| "fig-slider"
|
|
106
|
+
| "fig-spinner"
|
|
107
|
+
| "fig-swatch"
|
|
108
|
+
| "fig-switch"
|
|
109
|
+
| "fig-tab"
|
|
110
|
+
| "fig-tab-content"
|
|
111
|
+
| "fig-tabs"
|
|
112
|
+
| "fig-tooltip"
|
|
113
|
+
| "fig-truncate"
|
|
114
|
+
| "fig-video"
|
|
115
|
+
| "propskit-color"
|
|
116
|
+
| "propskit-color-point"
|
|
117
|
+
| "propskit-fill"
|
|
118
|
+
| "propskit-gradient"
|
|
119
|
+
| "propskit-group"
|
|
120
|
+
| "propskit-number"
|
|
121
|
+
| "propskit-oscillator"
|
|
122
|
+
| "propskit-point-point"
|
|
123
|
+
| "propskit-point-radius"
|
|
124
|
+
| "propskit-point-radius-angle"
|
|
125
|
+
| "propskit-position"
|
|
126
|
+
| "propskit-select"
|
|
127
|
+
| "propskit-slider"
|
|
128
|
+
| "propskit-switch"
|
|
129
|
+
| "propskit-text"
|
|
130
|
+
| "propskit-wheel";
|
|
131
|
+
|
|
132
|
+
declare module "react" {
|
|
133
|
+
namespace JSX {
|
|
134
|
+
interface IntrinsicElements
|
|
135
|
+
extends Record<FigTag, FigAttrs> {
|
|
136
|
+
dialog: React.DetailedHTMLProps<
|
|
137
|
+
React.DialogHTMLAttributes<HTMLDialogElement>,
|
|
138
|
+
HTMLDialogElement
|
|
139
|
+
> & { is?: string; [key: string]: unknown };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Host JSX
|
|
146
|
+
|
|
147
|
+
Render the custom elements as tags. Children are React nodes (`key` on lists). `className` works on `fig-*`, `propskit-*`, and `<dialog is="fig-…">`.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<fig-field direction="horizontal">
|
|
151
|
+
<label>Opacity</label>
|
|
152
|
+
<fig-slider
|
|
153
|
+
value={String(opacity)}
|
|
154
|
+
min="0"
|
|
155
|
+
max="100"
|
|
156
|
+
text="true"
|
|
157
|
+
units="%"
|
|
158
|
+
full
|
|
159
|
+
onInput={onInput}
|
|
160
|
+
onChange={onChange}
|
|
161
|
+
/>
|
|
162
|
+
</fig-field>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Attributes
|
|
166
|
+
|
|
167
|
+
- Strings: `text="true"`, `variant="ghost"`, `value={String(n)}`.
|
|
168
|
+
- Presence: `icon`, `full`, `compact`, `disabled` as boolean props; unset with `undefined`.
|
|
169
|
+
- Checked: `checked={on ? "true" : undefined}` — never `checked={false}`.
|
|
170
|
+
- Native dialog open: `open={open ? true : undefined}`.
|
|
171
|
+
- Optional flags / `data-*`: omit with `undefined`.
|
|
172
|
+
|
|
173
|
+
Passing `value` / `checked` as JSX props on re-render is OK. Components do not emit `input` from programmatic writes and ignore value writes during drag.
|
|
174
|
+
|
|
175
|
+
## Events
|
|
176
|
+
|
|
177
|
+
Use React handlers:
|
|
178
|
+
|
|
179
|
+
- `onClick` — `fig-button` and other clickable hosts
|
|
180
|
+
- `onInput` — live (`fig-switch`, `fig-slider`, number/color/text)
|
|
181
|
+
- `onChange` — commit (`fig-options`, `fig-dropdown`, often paired with `onInput`)
|
|
182
|
+
|
|
183
|
+
Read order:
|
|
184
|
+
|
|
185
|
+
1. `event.detail` (`.checked`, `.value`, or the detail payload)
|
|
186
|
+
2. `event.currentTarget.value` / `.checked`
|
|
187
|
+
3. `event.target.value`
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
function readValue(event: Event) {
|
|
191
|
+
const custom = event as CustomEvent<{ checked?: boolean; value?: unknown }>;
|
|
192
|
+
const host = event.currentTarget as HTMLElement & {
|
|
193
|
+
value?: unknown;
|
|
194
|
+
checked?: boolean;
|
|
195
|
+
};
|
|
196
|
+
if (typeof custom.detail?.checked === "boolean") return custom.detail.checked;
|
|
197
|
+
if (custom.detail?.value !== undefined) return custom.detail.value;
|
|
198
|
+
if (custom.detail !== undefined && custom.detail !== null) return custom.detail;
|
|
199
|
+
return host.value ?? host.checked;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Native `addEventListener` only when React does not map the event: dialog `close` / `cancel`, delegated host clicks, imperative APIs.
|
|
204
|
+
|
|
205
|
+
Common events: `input`, `change`, `loaded`, `optionhover`, `reorder`, `remove`, `modeready`, `webcamstream`, `close`.
|
|
206
|
+
|
|
207
|
+
## Customized built-ins
|
|
208
|
+
|
|
209
|
+
Do not use `<fig-dialog>`, `<fig-popup>`, or `<fig-toast>`. Use `<dialog is="…">`.
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
const buttonRef = useRef<HTMLElement>(null);
|
|
213
|
+
const popupRef = useRef<HTMLDialogElement>(null);
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
const popup = popupRef.current;
|
|
217
|
+
if (!popup) return;
|
|
218
|
+
(popup as HTMLDialogElement & { anchor?: Element | null }).anchor =
|
|
219
|
+
buttonRef.current;
|
|
220
|
+
const onClose = () => setOpen(false);
|
|
221
|
+
popup.addEventListener("close", onClose);
|
|
222
|
+
return () => popup.removeEventListener("close", onClose);
|
|
223
|
+
}, []);
|
|
224
|
+
|
|
225
|
+
<dialog
|
|
226
|
+
ref={popupRef}
|
|
227
|
+
is="fig-popup"
|
|
228
|
+
variant="popover"
|
|
229
|
+
position="bottom"
|
|
230
|
+
offset="0 8"
|
|
231
|
+
className="preferences-popup"
|
|
232
|
+
open={open ? true : undefined}
|
|
233
|
+
>
|
|
234
|
+
<fig-header>Preferences</fig-header>
|
|
235
|
+
<fig-content>…</fig-content>
|
|
236
|
+
</dialog>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Set `anchor` as an element on the ref after mount (selector strings also work).
|
|
240
|
+
|
|
241
|
+
## Imperative refs
|
|
242
|
+
|
|
243
|
+
`useRef<HTMLElement>(null)` on hosts. Tooltips:
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
tooltip.text = "Copied";
|
|
247
|
+
tooltip.showPopup?.();
|
|
248
|
+
tooltip.hidePopup?.();
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Toasts: `toast.showToast()`. Lab reset: `host.resetToDefault()`.
|
|
252
|
+
|
|
253
|
+
## Light DOM
|
|
254
|
+
|
|
255
|
+
React owns child nodes. Components slot; they do not relocate children.
|
|
256
|
+
|
|
257
|
+
- `fig-menu` items; trigger gets `slot="trigger"`
|
|
258
|
+
- `fig-select-options slot="panel"`
|
|
259
|
+
- `fig-tabs` / `fig-chooser` children
|
|
260
|
+
- media `slot="overlay"`
|
|
261
|
+
- fill custom `slot="mode-*"`
|
|
262
|
+
|
|
263
|
+
Do not `innerHTML` a tree React rendered. Do not reparent nodes React created. Children may arrive after `connectedCallback`; hosts already watch for that.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# FigUI3 core API reference
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Playground: `/figui3#{section}`.
|
|
3
|
+
Public API: `README.md`. React recipes: [components.md](components.md).
|
|
5
4
|
|
|
6
5
|
## Option string formats
|
|
7
6
|
|
|
@@ -84,8 +83,8 @@ import "@rogieking/figui3/fig-layer.css";
|
|
|
84
83
|
import "@rogieking/figui3/fig-layer.js";
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
Attrs: `open`, `visible`, `disabled`. Markup: child `.fig-layer-row` plus nested `fig-layer`. Events: `openchange`, `visibilitychange`.
|
|
86
|
+
Attrs: `open`, `visible`, `disabled`. Markup: child `.fig-layer-row` plus nested `fig-layer`. Events: `openchange`, `visibilitychange`.
|
|
88
87
|
|
|
89
88
|
## Native elements
|
|
90
89
|
|
|
91
|
-
|
|
90
|
+
Styled native `button`, `select`, `input`, `textarea`, `checkbox`, `.switch`, `radio`, `color`, `progress`, `fieldset`, `details`, `hr` when a custom element is unnecessary.
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: propkit
|
|
3
3
|
description: >-
|
|
4
|
-
Guides Figma-style property panel composition
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
raw fig-* rows and propskit-* wrappers (fig-lab).
|
|
4
|
+
Guides Figma-style property panel composition using fig-field rows and FigUI3
|
|
5
|
+
controls, including React JSX. Use when building labeled property panels or
|
|
6
|
+
choosing between raw fig-* rows and propskit-* wrappers (fig-lab).
|
|
8
7
|
user-invocable: false
|
|
9
8
|
---
|
|
10
9
|
|
|
@@ -12,54 +11,47 @@ user-invocable: false
|
|
|
12
11
|
|
|
13
12
|
Patterns for Figma property panels. Two layers:
|
|
14
13
|
|
|
15
|
-
| Surface |
|
|
16
|
-
|
|
17
|
-
| **
|
|
18
|
-
| **Lab wrappers** |
|
|
14
|
+
| Surface | What to use |
|
|
15
|
+
|---|---|
|
|
16
|
+
| **Core rows** | Horizontal `fig-field` + core `fig-*` |
|
|
17
|
+
| **Lab wrappers** | `propskit-*` (see `fig-lab` skill) |
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
Core control APIs: `figui3` skill. Select/fill picker: `fig-editor`. Labeled wrappers: `fig-lab`.
|
|
19
|
+
Core tags: `figui3` skill + [../figui3/components.md](../figui3/components.md). React: [../figui3/react.md](../figui3/react.md). Select/fill picker: `fig-editor`. Labeled wrappers: `fig-lab`.
|
|
22
20
|
|
|
23
21
|
## Principles
|
|
24
22
|
|
|
25
23
|
1. Default to horizontal `fig-field` rows.
|
|
26
24
|
2. One concise label per control.
|
|
27
25
|
3. For new labeled property controls in lab, prefer `propskit-*` over duplicating field chrome.
|
|
28
|
-
4.
|
|
29
|
-
5. Panel width ~240px.
|
|
30
|
-
|
|
31
|
-
## React bootstrap
|
|
32
|
-
|
|
33
|
-
```tsx
|
|
34
|
-
import "@rogieking/figui3/fig.css";
|
|
35
|
-
|
|
36
|
-
const bootstrap = async () => {
|
|
37
|
-
await import("@rogieking/figui3/fig.js");
|
|
38
|
-
createRoot(document.getElementById("app")!).render(<App />);
|
|
39
|
-
};
|
|
40
|
-
bootstrap();
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Add `fig-editor` when using `fig-select` / fill picker. Add `fig-lab` when using `propskit-*`.
|
|
44
|
-
|
|
45
|
-
On `fig-*` and `<dialog is="fig-...">`, use `class` not `className`.
|
|
26
|
+
4. Compose from `fig-*` unless the row needs lab wrappers.
|
|
27
|
+
5. Panel width ~240px. Keep density tight.
|
|
46
28
|
|
|
47
29
|
## Field composition
|
|
48
30
|
|
|
49
|
-
```
|
|
31
|
+
```tsx
|
|
50
32
|
<fig-field direction="horizontal">
|
|
51
33
|
<label>Opacity</label>
|
|
52
|
-
<fig-slider
|
|
34
|
+
<fig-slider
|
|
35
|
+
value={String(opacity)}
|
|
36
|
+
min="0"
|
|
37
|
+
max="100"
|
|
38
|
+
text="true"
|
|
39
|
+
units="%"
|
|
40
|
+
full
|
|
41
|
+
onInput={onInput}
|
|
42
|
+
onChange={onChange}
|
|
43
|
+
/>
|
|
53
44
|
</fig-field>
|
|
54
45
|
```
|
|
55
46
|
|
|
56
47
|
- Put control attrs on the control, not a wrapper.
|
|
57
48
|
- Use `full` when the control should stretch.
|
|
58
49
|
- Do not mix unrelated controls in one row unless grouped on purpose.
|
|
50
|
+
- Add `fig-editor` when using `fig-select` / fill picker. Add `fig-lab` when using `propskit-*`.
|
|
59
51
|
|
|
60
52
|
## Control heuristics
|
|
61
53
|
|
|
62
|
-
| Intent |
|
|
54
|
+
| Intent | Core | Lab wrapper |
|
|
63
55
|
|---|---|---|
|
|
64
56
|
| Boolean | `fig-switch` | `propskit-switch` |
|
|
65
57
|
| Continuous number | `fig-slider` | `propskit-slider` |
|
|
@@ -100,7 +92,5 @@ Use a horizontal fig-field, with a fig-slider, min=0 max=100 text=true units=%.
|
|
|
100
92
|
1. Identify intent (boolean, discrete, continuous, color/fill, media, motion).
|
|
101
93
|
2. Pick core vs lab wrapper.
|
|
102
94
|
3. Compose the row; set defaults explicitly.
|
|
103
|
-
4.
|
|
104
|
-
5. Verify
|
|
105
|
-
|
|
106
|
-
Primary files: `playground/src/data/sections.ts`, `playground/src/data/labSections.ts`, `fig.js`, `fig-lab.js`.
|
|
95
|
+
4. Wire `onInput` (live) and `onChange` (commit).
|
|
96
|
+
5. Verify keyboard.
|
package/package.json
CHANGED