@vitavision/ui 0.9.0 → 0.11.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/README.md CHANGED
@@ -123,13 +123,25 @@ keep independent preferences.
123
123
  ### Components
124
124
 
125
125
  **Primitives** — `Badge`, `CountRun`, `StatusDot` · `Button`, `ButtonLink` (a navigation styled as a button — never nest a `Button` in a link), `buttonClasses` · `Dialog`, `ConfirmDialog`,
126
- `DialogClose` · `Disclosure` · `Callout`, `Empty`, `ErrorBox`, `ProgressBar`, `Skeleton`,
127
- `SkeletonRows` · `Field` · `Input`, `NumberInput` (with an optional `unit` written in the
128
- field), `Textarea` · `VectorInput`, `PoseInput` · `PageHeader`, `Panel`,
129
- `ReadoutStrip`, `Section` · `SegmentedControl` · `Select` · `Slider` · `Table` ·
126
+ `DialogClose` · `Disclosure` · `DropdownMenu`, `MenuItem`, `MenuCheckboxItem`, `MenuLabel`,
127
+ `MenuSeparator` (a non-modal command and toggle menu) · `Callout`, `Empty`, `ErrorBox`,
128
+ `ProgressBar`, `Skeleton`, `SkeletonRows` · `Field` · `Input`, `NumberInput` (with an optional
129
+ `unit` written in the field; number-valued with `onValueChange`), `Textarea` · `VectorInput`,
130
+ `PoseInput` · `Kbd` · `Listbox` (single choice among options that render their own content) ·
131
+ `PageHeader`, `Panel`, `ReadoutStrip`, `Section` · `Popover`, `PopoverClose` ·
132
+ `SegmentedControl` · `Select` · `Slider` · `Table` ·
130
133
  `Checkbox`, `Switch` · `ToggleChip` · `Toaster`, `toast()` (notifications from anywhere; mount one `<Toaster />` near the root) ·
131
134
  `InfoHint`, `Tooltip`, `TooltipProvider` ·
132
- `ThemeToggle` · plus `cn`, `focusRing`, `focusRingInset`.
135
+ `ThemeToggle` · plus `cn`, `focusRing`, `focusRingInset`, and `formatNumber` / `parseNumber`
136
+ (the text ↔ number helpers behind the number fields).
137
+
138
+ A number-valued `NumberInput` keeps what is being typed and reports only finite numbers inside
139
+ `[min, max]`, so clearing a field to retype it never passes through 0. `onClear` is for a
140
+ quantity that may be unset:
141
+
142
+ ```tsx
143
+ <NumberInput unit="px" precision={1} min={4} value={roi.width} onValueChange={setWidth} />
144
+ ```
133
145
 
134
146
  `PoseInput` edits an SE(3) pose in the wire form `{ rotation: [qx, qy, qz, qw], translation:
135
147
  [tx, ty, tz] }` as a translation and roll/pitch/yaw in degrees, but does no rotation
package/dist/index.d.ts CHANGED
@@ -161,7 +161,10 @@ export declare function CountRun({ counts, className }: {
161
161
  * destroys something.
162
162
  */
163
163
  type ButtonVariant = "primary" | "secondary" | "ghost" | "danger";
164
- /** A button's height and padding. `md` is the comfortable density's size, `sm` the compact one's. */
164
+ /**
165
+ * A control's height and padding, shared by `Button`, `SegmentedControl` and `ToggleChip`. For a
166
+ * button, `md` is the comfortable density's size and `sm` the compact one's.
167
+ */
165
168
  type ButtonSize = "sm" | "md";
166
169
  /**
167
170
  * The classes that make a control look like a button, for the one element that has to be
@@ -335,6 +338,77 @@ export declare function Disclosure({ summary, count, defaultOpen, className, chi
335
338
  children: ReactNode;
336
339
  }): import("react").JSX.Element;
337
340
  //#endregion
341
+ //#region src/components/ui/DropdownMenu.d.ts
342
+ /** Props of `DropdownMenu`. */
343
+ interface DropdownMenuProps {
344
+ /** The element that opens it: one focusable element that accepts a ref (Radix `asChild`). */
345
+ trigger: ReactNode;
346
+ /** `MenuItem`, `MenuCheckboxItem`, `MenuLabel` and `MenuSeparator` elements. */
347
+ children: ReactNode;
348
+ /** Controlled open state. */
349
+ open?: boolean | undefined;
350
+ /** Called when it opens or closes. */
351
+ onOpenChange?: ((open: boolean) => void) | undefined;
352
+ /** Its alignment along the trigger. Defaults to `start`. */
353
+ align?: "start" | "center" | "end" | undefined;
354
+ /** The trigger's side it opens on. Defaults to `bottom`. */
355
+ side?: "top" | "right" | "bottom" | "left" | undefined;
356
+ /** Merged with the menu's own classes through `cn`. */
357
+ className?: string | undefined;
358
+ }
359
+ /**
360
+ * A menu (`role="menu"`) that opens from `trigger`, with arrow-key navigation, typeahead,
361
+ * Escape or a press outside to close, and focus returned to the trigger. It is non-modal: the
362
+ * page behind stays readable and operable. Toggling a `MenuCheckboxItem` keeps the menu open,
363
+ * so several layers can be switched in one visit.
364
+ */
365
+ export declare function DropdownMenu({ trigger, children, open, onOpenChange, align, side, className }: DropdownMenuProps): import("react").JSX.Element;
366
+ /** Props of `MenuItem`. */
367
+ interface MenuItemProps {
368
+ /** The item's text. */
369
+ children: ReactNode;
370
+ /** Runs the command. */
371
+ onSelect?: (() => void) | undefined;
372
+ /** An icon before the text, in the indicator column. */
373
+ icon?: ReactNode;
374
+ /** `defect` for a destructive command. */
375
+ tone?: "defect" | undefined;
376
+ /** A shortcut shown at the end of the row, as a `Kbd`. Display only: binding it is the app's. */
377
+ shortcut?: string | undefined;
378
+ /** Blocks the item. */
379
+ disabled?: boolean | undefined;
380
+ }
381
+ /**
382
+ * A command. `onSelect` runs when it is clicked or chosen with Enter, and the menu then
383
+ * closes. `tone="defect"` marks a destructive command.
384
+ */
385
+ export declare function MenuItem({ children, onSelect, shortcut, disabled, icon, tone }: MenuItemProps): import("react").JSX.Element;
386
+ /** Props of `MenuCheckboxItem`. */
387
+ interface MenuCheckboxItemProps {
388
+ /** The item's text. */
389
+ children: ReactNode;
390
+ /** Whether it is on. */
391
+ checked: boolean;
392
+ /** Called with the new state. */
393
+ onCheckedChange: (checked: boolean) => void;
394
+ /** A shortcut shown at the end of the row, as a `Kbd`. Display only: binding it is the app's. */
395
+ shortcut?: string | undefined;
396
+ /** Blocks the item. */
397
+ disabled?: boolean | undefined;
398
+ }
399
+ /**
400
+ * A toggle (`role="menuitemcheckbox"`): a layer on or off. A check marks it on. Choosing it
401
+ * leaves the menu open.
402
+ */
403
+ export declare function MenuCheckboxItem({ children, checked, onCheckedChange, shortcut, disabled }: MenuCheckboxItemProps): import("react").JSX.Element;
404
+ /** A heading over a group of items, in the eyebrow role. Not focusable. */
405
+ export declare function MenuLabel({ children }: {
406
+ /** The heading. */
407
+ children: ReactNode;
408
+ }): import("react").JSX.Element;
409
+ /** A divider between groups of items. */
410
+ export declare function MenuSeparator(): import("react").JSX.Element;
411
+ //#endregion
338
412
  //#region src/components/ui/Feedback.d.ts
339
413
  /** A callout's kind: a fact (`info`), a caveat (`warning`), a failure (`error`) or `success`. */
340
414
  type CalloutTone = "info" | "warning" | "error" | "success";
@@ -460,8 +534,40 @@ export declare function useControlHeight(): string;
460
534
  * is described by the field's description and error.
461
535
  */
462
536
  export declare function Input({ className, "aria-describedby": describedBy, ...rest }: ComponentProps<"input">): import("react").JSX.Element;
463
- /** Props of `NumberInput`: every `<input>` prop, `ref` included, with numeric bounds and a `unit`. */
464
- type NumberInputProps = Omit<ComponentProps<"input">, "min" | "max"> & {
537
+ /**
538
+ * Props of `NumberInput`: every `<input>` prop, `ref` included, with numeric bounds, a `unit`,
539
+ * and an optional number-valued API (`onValueChange`, `precision`).
540
+ */
541
+ type NumberInputProps = Omit<ComponentProps<"input">, "min" | "max" | "value"> & {
542
+ /**
543
+ * The value. As text, it is the native input's `value`. With `onValueChange` it is a
544
+ * number, and `null` shows an empty field.
545
+ */
546
+ value?: string | number | readonly string[] | null | undefined;
547
+ /**
548
+ * Makes the field number-valued.
549
+ *
550
+ * - **While focused** it keeps the text as typed. A keystroke is reported only when the
551
+ * text is a finite number inside `[min, max]` and differs from `value`. Empty, partial
552
+ * (`-`, `1e`) and out-of-range text is never reported, so clearing a field to retype it
553
+ * does not pass through 0, and typing `5` on the way to `50` does not report a value under
554
+ * `min`.
555
+ * - **Enter** shows the value at `precision`. **Escape** restores the value the field had
556
+ * on focus.
557
+ * - **On leaving**, the field shows `value` again.
558
+ */
559
+ onValueChange?: ((value: number) => void) | undefined;
560
+ /**
561
+ * Called when a number-valued field is committed empty (left, or Enter pressed, with no
562
+ * text), for a quantity that may be unset. Without it, emptying the field and leaving it
563
+ * restores `value`.
564
+ */
565
+ onClear?: (() => void) | undefined;
566
+ /**
567
+ * Decimals shown when a number-valued field is not being edited. Without it the number is
568
+ * shown as JavaScript prints it.
569
+ */
570
+ precision?: number | undefined;
465
571
  /** The smallest accepted value. */
466
572
  min?: number | undefined;
467
573
  /** The largest accepted value. */
@@ -485,14 +591,97 @@ type NumberInputProps = Omit<ComponentProps<"input">, "min" | "max"> & {
485
591
  * With a `unit`, the input sits in a `relative` full-width wrapper carrying `data-unit`, keeps
486
592
  * room on its right for the unit, and is described by it; size such a field through its
487
593
  * container. Without one — or with `""` — the markup is exactly the plain input.
594
+ *
595
+ * With `onValueChange` the field is number-valued: `value` is a number (`null` shows empty), the
596
+ * typed text survives while the field has focus, and only finite numbers inside `[min, max]`
597
+ * are reported (see `onValueChange`; `onClear` reports an emptied field). Without it the field
598
+ * is the native text-valued input.
488
599
  */
489
- export declare function NumberInput({ unit, ...props }: NumberInputProps): import("react").JSX.Element;
600
+ export declare function NumberInput({ onValueChange, onClear, precision, ...props }: NumberInputProps): import("react").JSX.Element;
490
601
  /**
491
602
  * Multi-line text — set in mono, for JSON and lists. Takes every `<textarea>` prop, `ref`
492
603
  * included.
493
604
  */
494
605
  export declare function Textarea({ className, "aria-describedby": describedBy, ...rest }: ComponentProps<"textarea">): import("react").JSX.Element;
495
606
  //#endregion
607
+ //#region src/components/ui/Kbd.d.ts
608
+ /**
609
+ * A keyboard key or chord (`F`, `⌘K`, `[`), set in mono on a raised key cap.
610
+ *
611
+ * Renders `<kbd>`, which screen readers announce as text. Write the key as the user's keyboard
612
+ * labels it; for a chord, one `Kbd` with the whole chord reads better than several. Takes
613
+ * every `<kbd>` prop, `ref` included.
614
+ */
615
+ export declare function Kbd({ className, ...rest }: ComponentProps<"kbd">): import("react").JSX.Element;
616
+ //#endregion
617
+ //#region src/components/ui/Listbox.d.ts
618
+ /** One option of a `Listbox`. */
619
+ interface ListboxOption {
620
+ /** Its value, unique in the list. */
621
+ value: string;
622
+ /** Its name: the default rendering, and what a screen reader announces. */
623
+ label: string;
624
+ /** A second line under the label, in the default rendering. */
625
+ description?: string | undefined;
626
+ /** Shown but not choosable. */
627
+ disabled?: boolean | undefined;
628
+ }
629
+ /** What `renderOption` is told about an option. */
630
+ interface ListboxOptionState {
631
+ /** It is the value. */
632
+ selected: boolean;
633
+ /** It has the keyboard cursor or the pointer. */
634
+ active: boolean;
635
+ }
636
+ /** Props of `Listbox`. */
637
+ interface ListboxProps {
638
+ /** The options, in order. */
639
+ options: readonly ListboxOption[];
640
+ /** The selected value, or `null` for none. */
641
+ value: string | null;
642
+ /** Called with the value of the option chosen by click, Enter or Space. */
643
+ onValueChange?: ((value: string) => void) | undefined;
644
+ /**
645
+ * Renders an option's content (the row keeps its own padding, states and accessibility).
646
+ * Defaults to the label, with the description under it.
647
+ */
648
+ renderOption?: ((option: ListboxOption, state: ListboxOptionState) => ReactNode) | undefined;
649
+ /** Names the list. */
650
+ "aria-label": string;
651
+ /** Focus the list when it mounts, e.g. when it opens in a `Popover`. */
652
+ autoFocus?: boolean | undefined;
653
+ /** Merged with the list's own classes through `cn`. */
654
+ className?: string | undefined;
655
+ }
656
+ /**
657
+ * A single-select list (`role="listbox"`) of options that each render their own content.
658
+ *
659
+ * The list is one tab stop. The arrow keys, Home, End and Page keys move the active option
660
+ * (`aria-activedescendant`), skipping disabled ones, and Enter or Space choose it. A click
661
+ * chooses the option under the pointer. Controlled: the caller keeps `value`. Each option
662
+ * carries `aria-selected`, `data-selected` and `data-active`; the list scrolls the active
663
+ * option into view.
664
+ */
665
+ export declare function Listbox({ options, value, onValueChange, renderOption, "aria-label": ariaLabel, autoFocus, className }: ListboxProps): import("react").JSX.Element;
666
+ //#endregion
667
+ //#region src/components/ui/numberText.d.ts
668
+ /**
669
+ * A number at a fixed number of decimals, without a negative zero (`-0.000` reads as a sign
670
+ * error in a coordinate readout).
671
+ *
672
+ * @param value - The number. Non-finite values print as `NaN`, `Infinity`, `-Infinity`.
673
+ * @param precision - Decimals, 0–20.
674
+ * @returns The text.
675
+ */
676
+ export declare function formatNumber(value: number, precision: number): string;
677
+ /**
678
+ * The number a field's text means, if it means one.
679
+ *
680
+ * @param text - What is in the field.
681
+ * @returns The number, or `null` for empty, partial (`-`, `1e`) or non-numeric text.
682
+ */
683
+ export declare function parseNumber(text: string): number | null;
684
+ //#endregion
496
685
  //#region src/components/ui/Panel.d.ts
497
686
  /**
498
687
  * A bordered surface with an optional header (a title and actions) above a padded body.
@@ -587,6 +776,49 @@ export declare function ReadoutStrip({ items, className }: {
587
776
  className?: string | undefined;
588
777
  }): import("react").JSX.Element | null;
589
778
  //#endregion
779
+ //#region src/components/ui/Popover.d.ts
780
+ /** Props of `Popover`. */
781
+ interface PopoverProps {
782
+ /**
783
+ * The element that opens it: one focusable element that accepts a ref (Radix `asChild`).
784
+ * It carries `aria-expanded` and `data-state` (`open` / `closed`).
785
+ */
786
+ trigger: ReactNode;
787
+ /** The content. */
788
+ children: ReactNode;
789
+ /** Controlled open state. */
790
+ open?: boolean | undefined;
791
+ /** Initial open state when uncontrolled. */
792
+ defaultOpen?: boolean | undefined;
793
+ /** Called when it opens or closes. */
794
+ onOpenChange?: ((open: boolean) => void) | undefined;
795
+ /** The trigger's side it opens on. Defaults to `bottom`. */
796
+ side?: "top" | "right" | "bottom" | "left" | undefined;
797
+ /** Its alignment along that side. Defaults to `start`. */
798
+ align?: "start" | "center" | "end" | undefined;
799
+ /** Names the content (`aria-label`), when nothing inside it does. */
800
+ "aria-label"?: string | undefined;
801
+ /** Merged with the content's own classes through `cn`. */
802
+ className?: string | undefined;
803
+ }
804
+ /**
805
+ * Floating content anchored to `trigger`, on the `overlay` surface with a shadow (floating
806
+ * layers are the only ones that cast one).
807
+ *
808
+ * Controlled (`open` / `onOpenChange`) or not (`defaultOpen`). Focus moves into the content
809
+ * when it opens and back to the trigger when it closes. Escape and a press outside close it.
810
+ * The content carries `data-state` and `data-side`.
811
+ */
812
+ export declare function Popover({ trigger, children, open, defaultOpen, onOpenChange, side, align, "aria-label": ariaLabel, className }: PopoverProps): import("react").JSX.Element;
813
+ /**
814
+ * Closes the surrounding `Popover` when its child is activated. Wraps one element (Radix
815
+ * `asChild`).
816
+ */
817
+ export declare function PopoverClose({ children }: {
818
+ /** The one element that closes the popover. */
819
+ children: ReactNode;
820
+ }): import("react").JSX.Element;
821
+ //#endregion
590
822
  //#region src/components/ui/PoseInput.d.ts
591
823
  /** Three numbers: a translation `[tx, ty, tz]`, or Euler angles. */
592
824
  type Vec3 = readonly [number, number, number];
@@ -661,8 +893,11 @@ export declare function PoseInput({ value, onValueChange, rotationView, translat
661
893
  * or `defaultValue` while `value` is `""` — and choosing the segment that matches
662
894
  * `defaultValue` reports `""`, so the default stays defined in one place. Each segment
663
895
  * carries `data-state="checked"` or `"unchecked"`; the group carries `data-disabled`.
896
+ *
897
+ * Toolbar-sized by default. In a row with a comfortable-density `Input`, `Select` or
898
+ * `Button`, pass `size="md"` and the strip's outer height matches theirs.
664
899
  */
665
- export declare function SegmentedControl({ value, defaultValue, options, onValueChange, disabled, "aria-label": ariaLabel, className }: {
900
+ export declare function SegmentedControl({ value, defaultValue, options, onValueChange, disabled, "aria-label": ariaLabel, size, className }: {
666
901
  /** `""` when the field is untouched. */
667
902
  value: string;
668
903
  /** What applies when nothing is sent. Highlighted while `value` is `""`. */
@@ -678,6 +913,13 @@ export declare function SegmentedControl({ value, defaultValue, options, onValue
678
913
  disabled?: boolean | undefined;
679
914
  /** Names the radio group. */
680
915
  "aria-label"?: string | undefined;
916
+ /**
917
+ * The strip's height: `sm` (the default) is the toolbar size, about 30px with `text-xs`
918
+ * labels; `md` is exactly 32px (`h-8`) outside, border included, with `text-sm` labels, the
919
+ * height of a comfortable-density `Input` or `Button`. Not derived from the density in
920
+ * force, so a consumer that never passes it renders as it always did.
921
+ */
922
+ size?: ButtonSize | undefined;
681
923
  /** Merged with the strip's own classes through `cn`. */
682
924
  className?: string | undefined;
683
925
  }): import("react").JSX.Element;
@@ -862,8 +1104,11 @@ export declare function Checkbox({ checked, onCheckedChange, onClick, label, des
862
1104
  * colour: a toolbar-sized `Switch` (`role="switch"`, `aria-checked`).
863
1105
  *
864
1106
  * Controlled. State is exposed as `data-state` (`checked`/`unchecked`).
1107
+ *
1108
+ * Toolbar-sized by default. In a row with a comfortable-density `Input`, `Select` or
1109
+ * `Button`, pass `size="md"` and the chip's outer height matches theirs.
865
1110
  */
866
- export declare function ToggleChip({ checked, onCheckedChange, children, swatch, disabled, title, className }: {
1111
+ export declare function ToggleChip({ checked, onCheckedChange, children, swatch, disabled, title, size, className }: {
867
1112
  /** Whether the layer is shown. */
868
1113
  checked: boolean;
869
1114
  /** Called with the new state. */
@@ -876,6 +1121,14 @@ export declare function ToggleChip({ checked, onCheckedChange, children, swatch,
876
1121
  disabled?: boolean | undefined;
877
1122
  /** Why the control is disabled, or what the layer is. Never the only explanation. */
878
1123
  title?: string | undefined;
1124
+ /**
1125
+ * The chip's height: `sm` (the default) is the toolbar size, about 26px with `text-xs`
1126
+ * text; `md` is exactly 32px (`h-8`) outside, border included, with `text-sm` text and a
1127
+ * slightly larger swatch, the height of a comfortable-density `Input` or `Button`. Not
1128
+ * derived from the density in force, so a consumer that never passes it renders as it
1129
+ * always did.
1130
+ */
1131
+ size?: ButtonSize | undefined;
879
1132
  /** Merged with the chip's own classes through `cn`. */
880
1133
  className?: string | undefined;
881
1134
  }): import("react").JSX.Element;
@@ -1043,8 +1296,8 @@ interface VectorInputProps {
1043
1296
  * A row of number fields editing one small vector, with the axis labels before the fields
1044
1297
  * and the unit once after them.
1045
1298
  *
1046
- * Controlled. A `role="group"` named by `aria-label`, each field named by its label and
1047
- * described by the unit. While a field has focus it shows what is typed; on blur or Enter it
1299
+ * Controlled. A `role="group"` named by `aria-label`. Each field is named by the group and its
1300
+ * label ("Translation x"), so two vectors on one screen stay apart, and is described by the unit. While a field has focus it shows what is typed; on blur or Enter it
1048
1301
  * shows the value at `precision`; Escape restores the value it had on focus. With `readOnly`
1049
1302
  * it renders a `ReadoutStrip` (`x 0.100 m · y …`) instead. The row carries `data-readonly`
1050
1303
  * and `data-disabled`.
@@ -1090,5 +1343,5 @@ export declare function Tabs<Id extends string>({ items, active, onSelect, label
1090
1343
  className?: string | undefined;
1091
1344
  }): import("react").JSX.Element;
1092
1345
  //#endregion
1093
- export type { BackLink, ButtonSize, ButtonVariant, CalloutTone, Column, Density, MeasureTone, NumberInputProps, PoseInputProps, PoseValue, Quaternion, ReadoutItem, RotationView, SelectOption, TabItem, ThemeChoice, ToastOptions, ToastRecord, ToastStore, ToastTone, ToasterProps, Tone, Vec3, VectorInputProps };
1346
+ export type { BackLink, ButtonSize, ButtonVariant, CalloutTone, Column, Density, DropdownMenuProps, ListboxOption, ListboxOptionState, ListboxProps, MeasureTone, MenuCheckboxItemProps, MenuItemProps, NumberInputProps, PopoverProps, PoseInputProps, PoseValue, Quaternion, ReadoutItem, RotationView, SelectOption, TabItem, ThemeChoice, ToastOptions, ToastRecord, ToastStore, ToastTone, ToasterProps, Tone, Vec3, VectorInputProps };
1094
1347
  //# sourceMappingURL=index.d.ts.map