@phreshos/react-ui 0.1.49 → 0.1.50

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/dist/button.d.ts CHANGED
@@ -25,7 +25,10 @@ interface ButtonOwnProps extends ControlProps, RadiusProps, MaterialOverrides, S
25
25
  /** A Button that acts: a native `<button>`. */
26
26
  export interface ButtonActionProps extends NativeButtonProps, ButtonOwnProps {
27
27
  readonly href?: undefined;
28
- /** Prevents activation while keeping the Button focusable. */
28
+ /**
29
+ * Work the Button started is under way: a Spinner shows in its place, the Button keeps its size
30
+ * and its name, and it stays focusable without activating.
31
+ */
29
32
  readonly pending?: boolean;
30
33
  }
31
34
  /**
package/dist/button.js CHANGED
@@ -1,8 +1,9 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { Button as AriaButton, Link as AriaLink } from "react-aria-components";
4
- import { controlOpacity, transition, useControlMetrics } from "./control/control.js";
4
+ import { transition, useControlMetrics } from "./control/control.js";
5
5
  import { surfaceRender } from "./control/surface-render.js";
6
+ import { Spinner } from "./spinner.js";
6
7
  /** A raised Surface you act on, or follow when it has an `href`. */
7
8
  export const Button = forwardRef(function Button(properties, ref) {
8
9
  const { children, color = "default", depth, disabled, iconOnly = false, radius, size, style, className, material, shadow, ...rest } = properties;
@@ -33,7 +34,7 @@ export const Button = forwardRef(function Button(properties, ref) {
33
34
  const { pending = false, type = "button", ...action } = rest;
34
35
  // Disabled state is read from React Aria, so a Button placed in a slot, such
35
36
  // as a calendar's month buttons, follows the owner that disables it.
36
- return _jsx(AriaButton, { ...action, ref: ref, type: type, className: className, isDisabled: disabled, isPending: pending, render: surfaceRender("button", state => surface(state, pending)), style: state => buttonStyle(metrics, iconOnly, !state.isDisabled && !pending && state.isPressed, state.isDisabled, pending, style), children: children });
37
+ return _jsx(AriaButton, { ...action, ref: ref, type: type, className: className, isDisabled: disabled, isPending: pending, render: surfaceRender("button", state => surface(state, pending)), style: state => buttonStyle(metrics, iconOnly, !state.isDisabled && !pending && state.isPressed, state.isDisabled, pending, style), children: pending ? _jsxs(_Fragment, { children: [_jsx("span", { style: { display: "inline-flex", alignItems: "center", gap: metrics.gap, opacity: 0 }, children: children }), _jsx("span", { style: { position: "absolute", inset: 0, display: "grid", placeItems: "center" }, children: _jsx(Spinner, { decorative: true, color: "currentColor", style: { width: metrics.indicator, height: metrics.indicator } }) })] }) : children });
37
38
  });
38
39
  function buttonStyle(metrics, iconOnly, pressed, disabled, pending, style) {
39
40
  return {
@@ -62,7 +63,7 @@ function buttonStyle(metrics, iconOnly, pressed, disabled, pending, style) {
62
63
  WebkitTapHighlightColor: "transparent",
63
64
  cursor: disabled ? "not-allowed" : pending ? "progress" : "pointer",
64
65
  scale: pressed ? "0.97" : "1",
65
- ...(pending ? { opacity: controlOpacity.pending } : {}),
66
+ ...(pending ? { position: "relative" } : {}),
66
67
  ...style
67
68
  };
68
69
  }
@@ -36,7 +36,6 @@ export declare const controlLineHeight = 1.45;
36
36
  export declare const controlFontWeight = 600;
37
37
  /** Attenuation shared by every control family. */
38
38
  export declare const controlOpacity: Readonly<{
39
- pending: 0.68;
40
39
  secondary: 0.66;
41
40
  placeholder: 0.55;
42
41
  }>;
@@ -21,7 +21,6 @@ export const controlLineHeight = 1.45;
21
21
  export const controlFontWeight = 600;
22
22
  /** Attenuation shared by every control family. */
23
23
  export const controlOpacity = Object.freeze({
24
- pending: 0.68,
25
24
  secondary: 0.66,
26
25
  placeholder: 0.55
27
26
  });
@@ -0,0 +1,21 @@
1
+ import { type CSSProperties, type ReactNode } from "react";
2
+ export interface DrawerProps {
3
+ readonly open: boolean;
4
+ /** Called for a press outside the Drawer or for Escape. */
5
+ readonly onClose: () => void;
6
+ /** Shown at its top, such as the name of what it belongs to. */
7
+ readonly title?: ReactNode;
8
+ readonly "aria-label"?: string;
9
+ readonly className?: string;
10
+ readonly style?: CSSProperties;
11
+ readonly children: ReactNode;
12
+ }
13
+ /**
14
+ * A narrow layout's sidebar, over its content from the start side, such as the navigation a narrow
15
+ * window gives up to its content. It is placed in its nearest positioned container. A press
16
+ * outside or Escape closes it. It slides in and back out along the same path, timed by the
17
+ * Appearance transaction, the way out its own animation so its end is seen; without motion it
18
+ * simply shows and goes. Only its position moves:
19
+ * fading it would switch off the blur it draws of what is behind it.
20
+ */
21
+ export declare function Drawer({ open, onClose, title, className, style, children, ...properties }: DrawerProps): import("react").JSX.Element | null;
package/dist/drawer.js ADDED
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { controlFontSizes, controlFontWeight } from "./control/control.js";
4
+ import MotionStyle, { overlayTransition } from "./foundation/motion-style.js";
5
+ import { scale } from "./foundation/scale.js";
6
+ import { useVisual } from "./foundation/visual.js";
7
+ import { Surface } from "./surface/surface.js";
8
+ /**
9
+ * A narrow layout's sidebar, over its content from the start side, such as the navigation a narrow
10
+ * window gives up to its content. It is placed in its nearest positioned container. A press
11
+ * outside or Escape closes it. It slides in and back out along the same path, timed by the
12
+ * Appearance transaction, the way out its own animation so its end is seen; without motion it
13
+ * simply shows and goes. Only its position moves:
14
+ * fading it would switch off the blur it draws of what is behind it.
15
+ */
16
+ export function Drawer({ open, onClose, title, className, style, children, ...properties }) {
17
+ const visual = useVisual();
18
+ const animated = visual.duration > 0;
19
+ const inset = scale(visual.spacing, "small");
20
+ // It stays while it slides out, and leaves once the slide ends.
21
+ const [present, setPresent] = useState(open);
22
+ useEffect(() => { if (open)
23
+ setPresent(true);
24
+ else if (!animated)
25
+ setPresent(false); }, [open, animated]);
26
+ useEffect(() => {
27
+ if (!open)
28
+ return;
29
+ // Escape closes the Drawer alone, not a surface that holds it.
30
+ const close = (event) => { if (event.key === "Escape") {
31
+ event.preventDefault();
32
+ event.stopPropagation();
33
+ onClose();
34
+ } };
35
+ addEventListener("keydown", close, true);
36
+ return () => removeEventListener("keydown", close, true);
37
+ }, [open, onClose]);
38
+ if (!present)
39
+ return null;
40
+ return _jsxs(_Fragment, { children: [_jsx(MotionStyle, {}), open && _jsx("div", { "data-drawer-scrim": "", style: { position: "absolute", inset: 0, zIndex: 10 }, onPointerDown: onClose }), _jsxs(Surface, { ...properties, "data-drawer": "", "data-state": animated ? open ? "opening" : "closing" : undefined, material: "full", className: ["phreshos-ui-drawer", className].filter(Boolean).join(" "), onAnimationEnd: () => { if (!open)
41
+ setPresent(false); }, style: {
42
+ ...overlayTransition(visual),
43
+ position: "absolute",
44
+ insetBlock: inset,
45
+ insetInlineStart: inset,
46
+ zIndex: 11,
47
+ boxSizing: "border-box",
48
+ width: `min(${visual.spacing * 20}px, calc(100% - ${visual.spacing * 4}px))`,
49
+ padding: inset,
50
+ overflow: "auto",
51
+ ...style
52
+ }, children: [title != null && _jsx("div", { style: { padding: `${inset}px ${inset}px ${visual.spacing}px`, fontSize: controlFontSizes.xlarge, fontWeight: controlFontWeight }, children: title }), children] })] });
53
+ }
@@ -45,6 +45,22 @@ const stylesheet = `
45
45
  color: inherit;
46
46
  opacity: 0.55;
47
47
  }
48
+ .phreshos-ui-text-control::-webkit-search-cancel-button,
49
+ .phreshos-ui-text-control::-webkit-search-decoration {
50
+ display: none;
51
+ }
52
+ .phreshos-ui-drawer[data-state="opening"] {
53
+ animation: phreshos-ui-drawer-in var(--phreshos-ui-motion-duration) var(--phreshos-ui-motion-easing);
54
+ }
55
+ .phreshos-ui-drawer[data-state="closing"] {
56
+ animation: phreshos-ui-drawer-out var(--phreshos-ui-motion-duration) var(--phreshos-ui-motion-easing) forwards;
57
+ }
58
+ :dir(rtl).phreshos-ui-drawer[data-state="opening"] { animation-name: phreshos-ui-drawer-in-rtl; }
59
+ :dir(rtl).phreshos-ui-drawer[data-state="closing"] { animation-name: phreshos-ui-drawer-out-rtl; }
60
+ @keyframes phreshos-ui-drawer-in { from { translate: calc(-100% - 1rem) 0; } }
61
+ @keyframes phreshos-ui-drawer-out { to { translate: calc(-100% - 1rem) 0; } }
62
+ @keyframes phreshos-ui-drawer-in-rtl { from { translate: calc(100% + 1rem) 0; } }
63
+ @keyframes phreshos-ui-drawer-out-rtl { to { translate: calc(100% + 1rem) 0; } }
48
64
  @keyframes phreshos-ui-spin {
49
65
  to { rotate: 360deg; }
50
66
  }
package/dist/main.d.ts CHANGED
@@ -17,6 +17,7 @@ export { resolveColor, useColor, useContrastingColor, type Color, type ColorLeve
17
17
  export { Panel, type PanelProps, type PanelHeaderProps, type PanelContentProps } from "./panel.js";
18
18
  export { Window, type WindowProps, type WindowContentProps, type WindowHeaderProps, type WindowIdentityProps, type WindowCenterProps, type WindowActionsProps, type WindowActionProps, type WindowControlProps, type WindowMaximizeProps, type WindowCloseProps, type WindowMoveCaptureProps } from "./window.js";
19
19
  export { default as useWindowMoveHandle, type WindowMoveHandle } from "./use-window-move-handle.js";
20
+ export { Drawer, type DrawerProps } from "./drawer.js";
20
21
  export { Button, type ButtonActionProps, type ButtonColor, type ButtonLinkProps, type ButtonProps } from "./button.js";
21
22
  export { Input, type InputProps } from "./input.js";
22
23
  export { Textarea, type TextareaProps } from "./textarea.js";
package/dist/main.js CHANGED
@@ -17,6 +17,7 @@ export { resolveColor, useColor, useContrastingColor } from "./foundation/color.
17
17
  export { Panel } from "./panel.js";
18
18
  export { Window } from "./window.js";
19
19
  export { default as useWindowMoveHandle } from "./use-window-move-handle.js";
20
+ export { Drawer } from "./drawer.js";
20
21
  export { Button } from "./button.js";
21
22
  export { Input } from "./input.js";
22
23
  export { Textarea } from "./textarea.js";
package/dist/spinner.js CHANGED
@@ -1,9 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { ProgressBar as AriaProgressBar } from "react-aria-components";
4
4
  import { useControlMetrics } from "./control/control.js";
5
- import { colorOpacity, resolveColor } from "./foundation/color.js";
6
- import MotionStyle from "./foundation/motion-style.js";
5
+ import { resolveColor } from "./foundation/color.js";
6
+ import MotionStyle, { loopDuration } from "./foundation/motion-style.js";
7
7
  import { scale } from "./foundation/scale.js";
8
8
  /** A standalone status stays visibly distinct from the compact control indicator. */
9
9
  export function spinnerDiameter(spacing, size) {
@@ -25,7 +25,7 @@ export const Spinner = forwardRef(function Spinner({ className, color = "primary
25
25
  verticalAlign: "middle",
26
26
  ...style
27
27
  };
28
- const indicator = _jsxs(_Fragment, { children: [_jsx(MotionStyle, {}), _jsxs("svg", { "data-spinner-indicator": "", "aria-hidden": "true", focusable: "false", viewBox: "0 0 24 24", width: "100%", height: "100%", fill: "none", style: { display: "block", animation: metrics.visual.duration > 0 ? `phreshos-ui-spin ${Math.max(600, metrics.visual.appearance.transaction.duration * 5)}ms linear infinite` : undefined }, children: [_jsx("circle", { "data-spinner-track": "", cx: "12", cy: "12", r: "9", stroke: colorOpacity(arc, 0.2), strokeWidth: "3" }), _jsx("circle", { "data-spinner-fill": "", cx: "12", cy: "12", r: "9", pathLength: "100", stroke: arc, strokeWidth: "3", strokeLinecap: "round", strokeDasharray: "30 70" })] })] });
28
+ const indicator = _jsxs(_Fragment, { children: [_jsx(MotionStyle, {}), _jsx("svg", { "data-spinner-indicator": "", "aria-hidden": "true", focusable: "false", viewBox: "0 0 24 24", width: "100%", height: "100%", fill: "none", style: { display: "block", animation: metrics.visual.duration > 0 ? `phreshos-ui-spin ${loopDuration(metrics.visual)}ms linear infinite` : undefined }, children: _jsx("circle", { "data-spinner-fill": "", cx: "12", cy: "12", r: "11", pathLength: "100", stroke: arc, strokeWidth: "2", strokeDasharray: "75 25", transform: "rotate(-90 12 12)" }) })] });
29
29
  if (decorative)
30
30
  return _jsx("div", { ...properties, ref: ref, "aria-hidden": "true", className: className, style: rootStyle, children: indicator });
31
31
  return _jsx(AriaProgressBar, { ...properties, ref: ref, "aria-label": label, className: className, isIndeterminate: true, style: rootStyle, children: indicator });
package/dist/tooltip.d.ts CHANGED
@@ -4,7 +4,9 @@ import { type ButtonActionProps } from "./button.js";
4
4
  import { type AriaOverlayInternals, type OverlayRootProps } from "./control/open-state.js";
5
5
  import { type Direction } from "./foundation/direction.js";
6
6
  import { type SurfaceOwnProps } from "./surface/surface.js";
7
- export type TooltipRootProps = OverlayRootProps & Readonly<Pick<AriaTooltipTriggerProps, "delay" | "closeDelay" | "trigger">> & Readonly<{
7
+ export type TooltipRootProps = OverlayRootProps & Readonly<Pick<AriaTooltipTriggerProps, "closeDelay" | "trigger">> & Readonly<{
8
+ /** Milliseconds the pointer rests on the trigger before the Tooltip opens: four Appearance transactions by default. */
9
+ delay?: number;
8
10
  /** Whether the Tooltip is prevented from opening. */
9
11
  disabled?: boolean;
10
12
  /** Whether pressing the trigger closes the Tooltip. Defaults to `true`. */
@@ -18,6 +20,8 @@ export interface TooltipContentProps extends Omit<AriaTooltipProps, AriaOverlayI
18
20
  readonly children?: ReactNode;
19
21
  readonly className?: string;
20
22
  readonly dir?: Direction;
23
+ /** DOM container that owns the positioned overlay's coordinate space. */
24
+ readonly portalContainer?: Element;
21
25
  readonly style?: CSSProperties;
22
26
  }
23
27
  export declare const TooltipContent: import("react").ForwardRefExoticComponent<TooltipContentProps & import("react").RefAttributes<HTMLDivElement>>;
package/dist/tooltip.js CHANGED
@@ -12,17 +12,19 @@ import { useVisual } from "./foundation/visual.js";
12
12
  import { floatingShadow } from "./surface/shadow-options.js";
13
13
  import { Surface } from "./surface/surface.js";
14
14
  export function TooltipRoot({ children, delay, closeDelay, trigger, disabled, closeOnPress, ...state }) {
15
- return _jsx(AriaTooltipTrigger, { ...ariaOpenState(state), delay: delay, closeDelay: closeDelay, trigger: trigger, isDisabled: disabled, shouldCloseOnPress: closeOnPress, children: children });
15
+ // A pointer resting on a trigger for four Appearance transactions is asking what it is.
16
+ const rest = useVisual().appearance.transaction.duration * 4;
17
+ return _jsx(AriaTooltipTrigger, { ...ariaOpenState(state), delay: delay ?? rest, closeDelay: closeDelay, trigger: trigger, isDisabled: disabled, shouldCloseOnPress: closeOnPress, children: children });
16
18
  }
17
19
  export const TooltipTrigger = forwardRef(function TooltipTrigger(properties, ref) {
18
20
  return _jsx(Button, { ...properties, ref: ref });
19
21
  });
20
- export const TooltipContent = forwardRef(function TooltipContent({ children, className, color, material, radius, shadow, style, offset, placement = "top", dir, ...attributes }, ref) {
22
+ export const TooltipContent = forwardRef(function TooltipContent({ children, className, color, material, radius, shadow, style, offset, placement = "top", dir, portalContainer, ...attributes }, ref) {
21
23
  const visual = useVisual();
22
24
  const inset = scale(visual.spacing, "small");
23
25
  const transition = overlayTransition(visual);
24
26
  const direction = resolveDirection(dir, useDirection());
25
- return _jsxs(_Fragment, { children: [_jsx(MotionStyle, {}), _jsx(AriaTooltip, { ...attributes, ref: ref, dir: direction, offset: offset ?? inset, placement: resolveDirectionalPlacement(placement, direction), className: overlayMotionClass, style: transition, children: _jsx(Surface, { dir: direction, className: className, color: color ?? "foreground", material: material, radius: radius, shadow: shadow ?? floatingShadow(visual.shadow), style: {
27
+ return _jsxs(_Fragment, { children: [_jsx(MotionStyle, {}), _jsx(AriaTooltip, { ...attributes, ref: ref, dir: direction, offset: offset ?? inset, placement: resolveDirectionalPlacement(placement, direction), UNSTABLE_portalContainer: portalContainer, className: overlayMotionClass, style: transition, children: _jsx(Surface, { dir: direction, className: className, color: color ?? "foreground", material: material, radius: radius, shadow: shadow ?? floatingShadow(visual.shadow), style: {
26
28
  boxSizing: "border-box",
27
29
  maxWidth: "28em",
28
30
  paddingBlock: inset,
@@ -1,4 +1,4 @@
1
- import type { BeginWindowMoveGesture } from "@phreshos/core";
1
+ import type { BeginPresentationMoveGesture } from "@phreshos/core";
2
2
  import { type PointerEventHandler } from "react";
3
3
  export interface WindowMoveHandle {
4
4
  onPointerDown: PointerEventHandler<HTMLElement>;
@@ -8,4 +8,4 @@ export interface WindowMoveHandle {
8
8
  onLostPointerCapture: PointerEventHandler<HTMLElement>;
9
9
  }
10
10
  /** Turns one DOM element into a move handle for any compatible host gesture. */
11
- export default function useWindowMoveHandle(beginMoveGesture?: BeginWindowMoveGesture, onError?: (error: unknown) => void): WindowMoveHandle;
11
+ export default function useWindowMoveHandle(beginMoveGesture?: BeginPresentationMoveGesture, onError?: (error: unknown) => void): WindowMoveHandle;
package/dist/window.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { HTMLAttributes, ReactNode } from "react";
2
- import type { BeginWindowMoveGesture } from "@phreshos/core";
2
+ import type { BeginPresentationMoveGesture } from "@phreshos/core";
3
3
  import { type ButtonActionProps } from "./button.js";
4
4
  import { type Color } from "./foundation/color.js";
5
5
  import { type SurfaceProps } from "./surface/surface.js";
@@ -16,7 +16,7 @@ export interface WindowHeaderProps extends Omit<HTMLAttributes<HTMLDivElement>,
16
16
  /** Base color inherited by header-owned text and icons. */
17
17
  readonly color?: Color;
18
18
  /** Hands an intentional header drag to its host after the pointer threshold. */
19
- readonly beginMoveGesture?: BeginWindowMoveGesture;
19
+ readonly beginMoveGesture?: BeginPresentationMoveGesture;
20
20
  /** Whether the represented window is currently maximized. */
21
21
  readonly maximized?: boolean;
22
22
  /** Toggles the represented window between maximized and restored states. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/react-ui",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "description": "React components for coherent PhreshOS Program interfaces.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -57,7 +57,7 @@
57
57
  "dependencies": {
58
58
  "@base-ui/react": "^1.7.0",
59
59
  "@internationalized/date": "^3.12.3",
60
- "@phreshos/core": "^0.1.66",
60
+ "@phreshos/core": "^0.1.67",
61
61
  "colorjs.io": "^0.7.1",
62
62
  "lucide-react": "^1.48.0",
63
63
  "react-aria": "^3.51.0",