@rogieking/figui3 8.9.43 → 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.
@@ -0,0 +1,643 @@
1
+ # FigUI3 core components (`fig.js`)
2
+
3
+ React contract: [react.md](react.md). Attrs: [reference.md](reference.md).
4
+
5
+ Handlers below assume `onInput` / `onChange` / `onClick` from [react.md](react.md).
6
+
7
+ ## Buttons and inputs
8
+
9
+ ### `fig-button`
10
+
11
+ ```tsx
12
+ <fig-button variant="ghost" icon aria-label="Close" onClick={onClick}>
13
+ <fig-icon name="close" />
14
+ </fig-button>
15
+ ```
16
+
17
+ - Attrs: `variant` (`""` primary, `secondary`, `destructive`, `destructiveSecondary`, `destructiveGhost`, `destructiveLink`, `ghost`, `link`, `input`, `overlay`), `type` (`button`, `toggle`, `submit`, `select`, `upload`), `size` (`""`, `large`, `compact`), `align`, `selected`, `disabled`, `icon`, `close-dialog`
18
+ - Events: `click`; toggle also reflects `selected`
19
+
20
+ ### `fig-button-combo`
21
+
22
+ ```tsx
23
+ <fig-button-combo>
24
+ <fig-button>Save</fig-button>
25
+ <fig-button variant="secondary">Cancel</fig-button>
26
+ </fig-button-combo>
27
+ ```
28
+
29
+ - Attrs: none required. Children stay in light DOM.
30
+
31
+ ### `fig-dropdown`
32
+
33
+ ```tsx
34
+ <fig-dropdown value={value} variant="ghost" full onChange={onChange}>
35
+ <option value="left">Left</option>
36
+ <option value="center">Center</option>
37
+ </fig-dropdown>
38
+ ```
39
+
40
+ - Attrs: `value`, `type` (`select` | `dropdown`), `variant` (`ghost`), `full`, `disabled`
41
+ - Events: `input` / `change` → `currentTarget.value`
42
+ - Prefer `fig-select` (editor) for Figma-style menus.
43
+
44
+ ### `fig-combo-input`
45
+
46
+ ```tsx
47
+ <fig-combo-input
48
+ value={value}
49
+ options="Small,Medium,Large"
50
+ placeholder="Size"
51
+ full
52
+ onInput={onInput}
53
+ onChange={onChange}
54
+ />
55
+ ```
56
+
57
+ - Attrs: `value`, `options`, `placeholder`, `disabled`, `full`
58
+
59
+ ### `fig-input-text`
60
+
61
+ ```tsx
62
+ <fig-input-text
63
+ value={value}
64
+ placeholder="Name"
65
+ full
66
+ onInput={onInput}
67
+ onChange={onChange}
68
+ />
69
+ ```
70
+
71
+ - Attrs: `type` (`text`, `email`, `password`, `search`, `url`), `multiline`, `autoresize`, `resizable`, `disabled`, `readonly`, `placeholder`, `full`
72
+
73
+ ### `fig-input-number`
74
+
75
+ ```tsx
76
+ <fig-input-number
77
+ value={String(n)}
78
+ min="0"
79
+ max="100"
80
+ step="1"
81
+ units="%"
82
+ steppers="true"
83
+ full
84
+ onInput={onInput}
85
+ onChange={onChange}
86
+ />
87
+ ```
88
+
89
+ - Attrs: `min`, `max`, `step`, `precision`, `units`, `units-disallow`, `steppers`, `disabled`, `full`
90
+
91
+ ### `fig-input-combo`
92
+
93
+ ```tsx
94
+ <fig-input-combo>
95
+ <fig-input-text value={value} onInput={onInput} />
96
+ <fig-button>Go</fig-button>
97
+ </fig-input-combo>
98
+ ```
99
+
100
+ - Children stay in light DOM.
101
+
102
+ ### `fig-input-file`
103
+
104
+ ```tsx
105
+ <fig-input-file
106
+ label="Upload"
107
+ accepts="image/*"
108
+ multiple
109
+ onChange={onChange}
110
+ />
111
+ ```
112
+
113
+ - Attrs: `label`, `accepts`, `multiple`, `disabled`, `variant` (`input`, `primary`, `secondary`, `ghost`, `link`, `overlay`)
114
+ - Events: `change` / `input` with files on the host
115
+
116
+ ### `fig-checkbox`
117
+
118
+ ```tsx
119
+ <fig-checkbox
120
+ label="Enabled"
121
+ checked={on ? "true" : undefined}
122
+ onInput={onInput}
123
+ />
124
+ ```
125
+
126
+ - Attrs: `label`, `checked`, `disabled`, `value`
127
+ - Events: `input` / `change` → `detail.checked` ?? `host.checked`
128
+
129
+ ### `fig-radio`
130
+
131
+ ```tsx
132
+ <fig-radio
133
+ name="align"
134
+ value="left"
135
+ label="Left"
136
+ checked={value === "left" ? "true" : undefined}
137
+ onInput={onInput}
138
+ />
139
+ ```
140
+
141
+ - Attrs: `name`, `value`, `label`, `checked`, `disabled`
142
+
143
+ ### `fig-switch`
144
+
145
+ ```tsx
146
+ <fig-switch
147
+ checked={on ? "true" : undefined}
148
+ onInput={onInput}
149
+ />
150
+ ```
151
+
152
+ - Attrs: `checked`, `disabled`, `label`, `indeterminate`
153
+ - Events: `input` / `change` → `detail.checked` ?? `host.checked`
154
+
155
+ ### `fig-slider`
156
+
157
+ ```tsx
158
+ <fig-slider
159
+ value={String(opacity)}
160
+ min="0"
161
+ max="100"
162
+ step="1"
163
+ text="true"
164
+ units="%"
165
+ full
166
+ onInput={onInput}
167
+ onChange={onChange}
168
+ />
169
+ ```
170
+
171
+ - Attrs: `type` (`range`, `opacity`, `hue`, `stepper`, `delta`), `min`, `max`, `step`, `text`, `units`, `transform`, `color`, `default`, `variant` (`classic`), `placeholder`, `full`, `disabled`
172
+ - Events: `input` / `change` → `currentTarget.value`
173
+ - React: keep `value` a string; do not remount during drag. Opacity: set `color`. Delta: set `default`. Stepper: include a `<datalist>` of stops.
174
+
175
+ ### `fig-options`
176
+
177
+ ```tsx
178
+ <fig-options
179
+ options="Light,Dark"
180
+ value={isDark ? "Dark" : "Light"}
181
+ full
182
+ onChange={onChange}
183
+ />
184
+ ```
185
+
186
+ - Attrs: `options` (comma / newline / JSON), `value`, `full`, `disabled`
187
+ - Events: `input` / `change` → `detail` ?? `target.value`
188
+
189
+ ## Color and fill
190
+
191
+ ### `fig-input-color`
192
+
193
+ ```tsx
194
+ <fig-input-color
195
+ value={color}
196
+ text="true"
197
+ alpha="true"
198
+ full
199
+ onInput={onInput}
200
+ onChange={onChange}
201
+ />
202
+ ```
203
+
204
+ - Attrs: `value`, `text`, `alpha`, `full`, `disabled`. Forwards `picker-*` when `fig-fill-picker` is registered. Do not use `picker` / `picker-anchor`.
205
+ - Events: `input` / `change` → `detail.{ color, alpha, opacity }` plus legacy `value` / `hex` / `rgba`
206
+
207
+ ### `fig-input-fill`
208
+
209
+ ```tsx
210
+ <fig-input-fill
211
+ value={fillJson}
212
+ full
213
+ onInput={onInput}
214
+ onChange={onChange}
215
+ />
216
+ ```
217
+
218
+ - Attrs: `value` (JSON or string), `mode`, `webcam-mode`, `default-video`, `picker-*`, `disabled`, `full`
219
+ - Events: `input` / `change` with fill payload in `detail`
220
+ - Custom modes: include the name in `mode`; slot `slot="mode-<name>"`. Closed chrome matches image fills. In React, listen for `modeready` and mount into `e.detail.container` (see `fig-editor`).
221
+
222
+ ### `fig-input-palette`
223
+
224
+ ```tsx
225
+ <fig-input-palette
226
+ value='["#0D99FF","#14AE5C"]'
227
+ onInput={onInput}
228
+ onChange={onChange}
229
+ />
230
+ ```
231
+
232
+ - Attrs: `value`, `fixed`, `open`, `disabled`
233
+
234
+ ### `fig-input-gradient`
235
+
236
+ ```tsx
237
+ <fig-input-gradient
238
+ value={gradientJson}
239
+ mode="tip"
240
+ onInput={onInput}
241
+ onChange={onChange}
242
+ />
243
+ ```
244
+
245
+ - Attrs: `value`, `edit`, `mode` (`handle` | `tip`), `disabled`
246
+
247
+ ### `fig-swatch`
248
+
249
+ ```tsx
250
+ <fig-swatch background="#14AE5C" size="small" selected={selected || undefined} />
251
+ ```
252
+
253
+ - Attrs: `background`, `size` (`small` | `medium` | `large`), `selected`, `disabled`, `alpha`
254
+
255
+ ### `fig-chit`
256
+
257
+ ```tsx
258
+ <fig-chit background="#14AE5C" />
259
+ ```
260
+
261
+ - Alias-style color chip. Same idea as `fig-swatch`.
262
+
263
+ ### `fig-color-tip`
264
+
265
+ ```tsx
266
+ <fig-color-tip value="#0D99FF" control="color" />
267
+ ```
268
+
269
+ - Attrs: `value`, `control` (`color` | `add` | `remove`)
270
+
271
+ ## Layout and chrome
272
+
273
+ ### `fig-field`
274
+
275
+ ```tsx
276
+ <fig-field direction="horizontal">
277
+ <label>Opacity</label>
278
+ <fig-slider value="75" min="0" max="100" text="true" units="%" full />
279
+ </fig-field>
280
+ ```
281
+
282
+ - Attrs: `direction` (`horizontal` | `vertical`), `label`, `columns`
283
+ - Put control attrs on the control, not the field.
284
+
285
+ ### `fig-group`
286
+
287
+ ```tsx
288
+ <fig-group name="Appearance" collapsible open compact>
289
+ {children}
290
+ </fig-group>
291
+ ```
292
+
293
+ - Attrs: `name`, `collapsible`, `open`, `compact`
294
+
295
+ ### `fig-header`
296
+
297
+ ```tsx
298
+ <fig-header borderless compact>
299
+ <h3>Title</h3>
300
+ </fig-header>
301
+ ```
302
+
303
+ - Attrs: `borderless`, `compact`
304
+
305
+ ### `fig-footer`
306
+
307
+ ```tsx
308
+ <fig-footer sticky>
309
+ <fig-button>Save</fig-button>
310
+ </fig-footer>
311
+ ```
312
+
313
+ - Attrs: `borderless`, `sticky`
314
+
315
+ ### `fig-content`
316
+
317
+ ```tsx
318
+ <fig-content>Body</fig-content>
319
+ ```
320
+
321
+ ### `fig-tabs` / `fig-tab` / `fig-tab-content`
322
+
323
+ ```tsx
324
+ <fig-tabs value={tab} onChange={onChange}>
325
+ <fig-tab value="general" selected={tab === "general" || undefined}>
326
+ General
327
+ </fig-tab>
328
+ <fig-tab value="export" content="#export">
329
+ Export
330
+ </fig-tab>
331
+ </fig-tabs>
332
+ <fig-tab-content id="export">…</fig-tab-content>
333
+ ```
334
+
335
+ - Tabs: roving tabindex, `aria-controls` via `content="#id"`
336
+ - Children stay in light DOM (overflow chrome must not steal them)
337
+ - Events: `input` / `change` → `currentTarget.value`
338
+
339
+ ### `fig-segmented-control` / `fig-segment`
340
+
341
+ ```tsx
342
+ <fig-segmented-control value={align} onChange={onChange}>
343
+ <fig-segment value="left">Left</fig-segment>
344
+ <fig-segment value="center">Center</fig-segment>
345
+ </fig-segmented-control>
346
+ ```
347
+
348
+ - Radio-group pattern. Segments stay light-DOM children.
349
+ - Events: `input` / `change` → `currentTarget.value`
350
+
351
+ ### `fig-chooser` / `fig-choice`
352
+
353
+ ```tsx
354
+ <fig-chooser value={value} layout="vertical" onChange={onChange}>
355
+ <fig-choice value="a" selected={value === "a" || undefined}>
356
+ A
357
+ </fig-choice>
358
+ <fig-choice value="b">B</fig-choice>
359
+ </fig-chooser>
360
+ ```
361
+
362
+ - Attrs (chooser): `value`, `layout` (`vertical` | `horizontal` | `grid`), `columns`, `drag`, `loop`, `auto-scroll`, `scroll-behavior`
363
+ - Choice: always set `value`. Omit chooser `value` to select first; `value=""` means none.
364
+ - Children stay in light DOM.
365
+
366
+ ### `fig-separator` / `fig-menu-separator`
367
+
368
+ ```tsx
369
+ <fig-separator label="Darken" sticky />
370
+ ```
371
+
372
+ - Attrs: `label`, `sticky`, `borderless`. First separator in a panel is auto-`borderless`.
373
+
374
+ ### `fig-menu` / `fig-menu-item`
375
+
376
+ ```tsx
377
+ <fig-menu position="bottom left" offset="8 8" onChange={onChange}>
378
+ <fig-button slot="trigger">Menu</fig-button>
379
+ <fig-menu-item value="copy">Copy</fig-menu-item>
380
+ <fig-separator />
381
+ <fig-menu-item value="paste" disabled>
382
+ Paste
383
+ </fig-menu-item>
384
+ </fig-menu>
385
+ ```
386
+
387
+ - Attrs (menu): `position`, `offset`, `closedby` (`auto` | `any` | `none`), `open`, `disabled`, `trigger="contextmenu"`
388
+ - Item: `value`, `disabled`, `subtle`. Also valid as a row in `fig-popup`.
389
+ - Trigger: `slot="trigger"` (also assigned automatically). Items stay in light DOM and slot into the popup — do not relocate.
390
+ - Internal popup uses `popover="manual"` (top layer) so lists work inside `fig-popup variant="popover"`.
391
+ - Events: `change` → `detail.{ value }`
392
+
393
+ ### `fig-icon`
394
+
395
+ ```tsx
396
+ <fig-icon name="search" size="small" color="secondary" />
397
+ ```
398
+
399
+ - Attrs: `name`, `size` (`medium` | `small`), `color` (token)
400
+
401
+ ### `fig-avatar`
402
+
403
+ ```tsx
404
+ <fig-avatar name="Rogie King" src={src} size="large" />
405
+ ```
406
+
407
+ - Attrs: `src`, `name`, `size` (`""` | `large`)
408
+
409
+ ### `fig-truncate`
410
+
411
+ ```tsx
412
+ <fig-truncate position="middle" tooltip tail="…">
413
+ A very long layer name
414
+ </fig-truncate>
415
+ ```
416
+
417
+ - Attrs: `position` (`right` | `left` | `middle`), `tooltip`, `tail`
418
+
419
+ ## Overlays
420
+
421
+ ### `dialog is="fig-dialog"`
422
+
423
+ ```tsx
424
+ <dialog is="fig-dialog" drag handle="fig-header" modal>
425
+ <fig-header>
426
+ Title
427
+ <fig-button variant="ghost" icon close-dialog aria-label="Close">
428
+ <fig-icon name="close" />
429
+ </fig-button>
430
+ </fig-header>
431
+ <fig-content>Body</fig-content>
432
+ </dialog>
433
+ ```
434
+
435
+ - Attrs: `modal`, `drag`, `resizable`, `autoresize`, `handle`, `closedby` (`any` | `closerequest` | `none`), `position` (viewport: `top left` … `bottom right`). No `anchor`.
436
+ - Events: native `close` / `cancel` — listen on the dialog ref.
437
+
438
+ ### `dialog is="fig-popup"`
439
+
440
+ ```tsx
441
+ <dialog
442
+ ref={popupRef}
443
+ is="fig-popup"
444
+ variant="popover"
445
+ position="bottom left"
446
+ offset="8 8"
447
+ className="menu"
448
+ open={open ? true : undefined}
449
+ >
450
+ <fig-content>…</fig-content>
451
+ </dialog>
452
+ ```
453
+
454
+ - Attrs: `anchor` (selector or element on the ref), `position`, `offset`, `viewport-margin`, `variant` (`popover` | `tooltip`), `theme` (`default` | `light` | `dark` | `menu`), `title` (auto header)
455
+ - `variant="popover"` uses CSS `filter` (containing block for `position: fixed`). Nested `fig-menu` / `fig-select` keep `popover="manual"`.
456
+ - Set `anchor` as an element after mount. Listen for `close`.
457
+
458
+ ### `dialog is="fig-toast"`
459
+
460
+ ```tsx
461
+ <dialog ref={toastRef} is="fig-toast" theme="success" duration="3000" dismiss>
462
+ Saved
463
+ </dialog>
464
+ ```
465
+
466
+ ```tsx
467
+ toastRef.current?.showToast();
468
+ ```
469
+
470
+ - Attrs: `theme`, `duration`, `offset`, `dismiss`, `live` (`polite` | `assertive`), `icon`
471
+
472
+ ### `fig-tooltip`
473
+
474
+ ```tsx
475
+ <fig-tooltip ref={tipRef} text="Copy command">
476
+ <fig-button variant="ghost" icon onClick={onCopy} aria-label="Copy">
477
+ <fig-icon name="copy" />
478
+ </fig-button>
479
+ </fig-tooltip>
480
+ ```
481
+
482
+ - Attrs: `text`, `action` (`hover` | `click` | `manual`), `delay`, `theme`, `pointer`, `show`
483
+ - Imperative: `tooltip.text = "Copied"; tooltip.showPopup(); tooltip.hidePopup();`
484
+
485
+ ## Media
486
+
487
+ ### `fig-preview`
488
+
489
+ ```tsx
490
+ <fig-preview aspect-ratio="16/9" fit="cover" checkerboard>
491
+ <img src={src} alt="" />
492
+ </fig-preview>
493
+ ```
494
+
495
+ - Attrs: `aspect-ratio`, `fit`, `full`, `checkerboard`
496
+ - Overlay: `slot="overlay"` — stays light DOM.
497
+
498
+ ### `fig-media`
499
+
500
+ ```tsx
501
+ <fig-media
502
+ type="image"
503
+ src={src}
504
+ fit="cover"
505
+ checkerboard="true"
506
+ upload
507
+ onChange={onChange}
508
+ />
509
+ ```
510
+
511
+ - Attrs: `type`, `src`, `caption`, `aspect-ratio`, `fit`, `upload`, `loading-indicator`, `checkerboard`, `controls`, `autoplay`, `loop`, `muted`, `poster`
512
+ - Events: `loaded` (`detail.src`), `input` / `change`
513
+
514
+ ### `fig-image`
515
+
516
+ ```tsx
517
+ <fig-image src={src} fit="cover" upload checkerboard="true" onChange={onChange} />
518
+ ```
519
+
520
+ - Attrs: same media surface attrs. `loaded` → `detail.src`
521
+
522
+ ### `fig-video`
523
+
524
+ ```tsx
525
+ <fig-video poster={poster} muted fit="cover" controls="true" />
526
+ ```
527
+
528
+ - Controls render **below** the preview, not as an overlay.
529
+
530
+ ### `fig-card`
531
+
532
+ ```tsx
533
+ <fig-card
534
+ src={src}
535
+ label="Card"
536
+ aspect-ratio="1/1"
537
+ selected={selected || undefined}
538
+ onClick={onClick}
539
+ />
540
+ ```
541
+
542
+ - Attrs: `src`, `label`, `sublabel`, `selected`, `disabled`, `full`, `size`, `aspect-ratio`, `fit`, `label-line-clamp`
543
+
544
+ ### `fig-media-controls`
545
+
546
+ ```tsx
547
+ <fig-media-controls
548
+ duration="120"
549
+ time={String(time)}
550
+ playing={playing || undefined}
551
+ onInput={onInput}
552
+ />
553
+ ```
554
+
555
+ - Attrs: `playing`, `overlay`, `disabled`, `duration`, `time`
556
+
557
+ ## Specialized
558
+
559
+ ### `fig-easing-curve`
560
+
561
+ ```tsx
562
+ <fig-easing-curve value="ease-in-out" onInput={onInput} onChange={onChange} />
563
+ ```
564
+
565
+ ### `fig-3d-rotate`
566
+
567
+ ```tsx
568
+ <fig-3d-rotate value='{"x":0,"y":0}' onInput={onInput} onChange={onChange} />
569
+ ```
570
+
571
+ - JSON `value`. Pass as a string attr.
572
+
573
+ ### `fig-origin-grid`
574
+
575
+ ```tsx
576
+ <fig-origin-grid value="50% 50%" fields="true" onInput={onInput} />
577
+ ```
578
+
579
+ ### `fig-joystick`
580
+
581
+ ```tsx
582
+ <fig-joystick value="50% 50%" axis-labels="X Y" onInput={onInput} />
583
+ ```
584
+
585
+ ### `fig-handle`
586
+
587
+ ```tsx
588
+ <div style={{ position: "relative", width: 120, height: 80 }}>
589
+ <fig-handle value="50% 50%" drag type="default" />
590
+ </div>
591
+ ```
592
+
593
+ - Attrs: `type` (`default` | `minimal` | `color` | `canvas`), `tip`, `size`, `color`, `selected`, `disabled`, `drag`, `drag-axes`, `drag-snapping`
594
+
595
+ ### `fig-spinner`
596
+
597
+ ```tsx
598
+ <fig-spinner size="small" />
599
+ ```
600
+
601
+ ### `fig-shimmer`
602
+
603
+ ```tsx
604
+ <fig-shimmer>
605
+ <span>Thinking…</span>
606
+ </fig-shimmer>
607
+ ```
608
+
609
+ - Attrs: `duration`, `direction`, `playing`
610
+
611
+ ### `fig-skeleton`
612
+
613
+ ```tsx
614
+ <fig-skeleton>
615
+ <fig-field>
616
+ <label>Name</label>
617
+ <fig-input-text value="Loading" />
618
+ </fig-field>
619
+ </fig-skeleton>
620
+ ```
621
+
622
+ - Attrs: `duration`, `direction`, `playing`
623
+
624
+ ## `fig-layer` (separate bundle)
625
+
626
+ Not registered by `fig.js`. Import `fig-layer.css` + `fig-layer.js`.
627
+
628
+ ```tsx
629
+ <fig-layer data-section="button" open>
630
+ <div className="fig-layer-row">
631
+ <label>Button</label>
632
+ </div>
633
+ <fig-layer data-section="button" data-example="primary">
634
+ <div className="fig-layer-row">
635
+ <label>Primary</label>
636
+ </div>
637
+ </fig-layer>
638
+ </fig-layer>
639
+ ```
640
+
641
+ - Attrs: `open`, `visible`, `disabled`, `selected`
642
+ - Events: `openchange`, `visibilitychange`. Nested layers are React children; sync `selected` / `open` via attrs or `setAttribute` after render.
643
+ - Clicks on `.fig-layer-chevron` toggle; row clicks are yours (delegate on a parent).