@phreshos/react-ui 0.1.45 → 0.1.47

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
@@ -5,12 +5,20 @@ import type { Color } from "./foundation/color.js";
5
5
  import type { RadiusProps } from "./foundation/radius.js";
6
6
  import type { MaterialOverrides } from "./surface/material-options.js";
7
7
  import type { ShadowOverrides } from "./surface/shadow-options.js";
8
+ import type { SurfaceDepth } from "./surface/surface.js";
8
9
  type NativeButtonProps = Omit<AriaButtonProps, "children" | "className" | "color" | "isDisabled" | "isPending" | "onClick" | "onPress" | "render" | "style">;
9
10
  type NativeLinkProps = Omit<AriaLinkProps, "children" | "className" | "color" | "href" | "isDisabled" | "onClick" | "onPress" | "render" | "style">;
10
11
  /** A color from Appearance or CSS; omission keeps the Button neutral. */
11
12
  export type ButtonColor = Color;
12
13
  interface ButtonOwnProps extends ControlProps, RadiusProps, MaterialOverrides, ShadowOverrides {
14
+ /** Raised by default; `flat` keeps its color level with its surroundings. */
15
+ readonly depth?: Exclude<SurfaceDepth, "recessed">;
13
16
  readonly children?: ReactNode;
17
+ /**
18
+ * The Button holds only an icon: it is square, as wide as it is tall. Name it
19
+ * with `aria-label`, since no text does.
20
+ */
21
+ readonly iconOnly?: boolean;
14
22
  /** Runs once for a normalized pointer, Enter, or Space activation. */
15
23
  readonly onPress?: () => void;
16
24
  }
package/dist/button.js CHANGED
@@ -5,10 +5,11 @@ import { controlOpacity, transition, useControlMetrics } from "./control/control
5
5
  import { surfaceRender } from "./control/surface-render.js";
6
6
  /** A raised Surface you act on, or follow when it has an `href`. */
7
7
  export const Button = forwardRef(function Button(properties, ref) {
8
- const { children, color = "default", disabled, radius, size, style, className, material, shadow, ...rest } = properties;
8
+ const { children, color = "default", depth, disabled, iconOnly = false, radius, size, style, className, material, shadow, ...rest } = properties;
9
9
  const metrics = useControlMetrics(size, radius);
10
10
  const surface = (state, pending) => ({
11
11
  color,
12
+ depth,
12
13
  material,
13
14
  shadow,
14
15
  radius: metrics.radius,
@@ -27,14 +28,14 @@ export const Button = forwardRef(function Button(properties, ref) {
27
28
  return state.isDisabled
28
29
  ? surfaceRender("span", state => surface(state, false))(inert, state)
29
30
  : surfaceRender("a", state => surface(state, false))(native, state);
30
- }, style: state => buttonStyle(metrics, !state.isDisabled && state.isPressed, state.isDisabled, false, style), children: children });
31
+ }, style: state => buttonStyle(metrics, iconOnly, !state.isDisabled && state.isPressed, state.isDisabled, false, style), children: children });
31
32
  }
32
33
  const { pending = false, type = "button", ...action } = rest;
33
34
  // Disabled state is read from React Aria, so a Button placed in a slot, such
34
35
  // as a calendar's month buttons, follows the owner that disables it.
35
- 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, !state.isDisabled && !pending && state.isPressed, state.isDisabled, pending, style), children: children });
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 });
36
37
  });
37
- function buttonStyle(metrics, pressed, disabled, pending, style) {
38
+ function buttonStyle(metrics, iconOnly, pressed, disabled, pending, style) {
38
39
  return {
39
40
  ...transition(metrics.visual, "box-shadow, color, outline-color, opacity, scale"),
40
41
  appearance: "none",
@@ -46,8 +47,9 @@ function buttonStyle(metrics, pressed, disabled, pending, style) {
46
47
  flexShrink: 0,
47
48
  minWidth: 0,
48
49
  height: metrics.height,
50
+ ...(iconOnly ? { width: metrics.height } : {}),
49
51
  paddingBlock: 0,
50
- paddingInline: metrics.inset,
52
+ paddingInline: iconOnly ? 0 : metrics.inset,
51
53
  border: 0,
52
54
  background: "none",
53
55
  font: "inherit",
@@ -16,7 +16,7 @@ export function CalendarLayout({ color, metrics, range = false, size }) {
16
16
  const cell = metrics.height;
17
17
  const single = useContext(CalendarStateContext);
18
18
  const ranged = useContext(RangeCalendarStateContext);
19
- return _jsxs(_Fragment, { children: [_jsxs("header", { style: { display: "grid", gridTemplateColumns: "auto 1fr auto", alignItems: "center", gap: metrics.gap }, children: [_jsx(Button, { slot: "previous", size: size, "aria-label": "Previous month", style: { width: metrics.height, paddingInline: 0 }, children: _jsx(Chevron, { direction: direction === "rtl" ? "next" : "previous" }) }), _jsx(CalendarHeading, { style: { margin: 0, textAlign: "center", fontFamily: "inherit", fontSize: "inherit", fontStyle: "inherit", lineHeight: "inherit", fontWeight: controlFontWeight } }), _jsx(Button, { slot: "next", size: size, "aria-label": "Next month", style: { width: metrics.height, paddingInline: 0 }, children: _jsx(Chevron, { direction: direction === "rtl" ? "previous" : "next" }) })] }), _jsxs(CalendarGrid, { style: { width: "max-content", margin: 0, borderCollapse: "separate", borderSpacing: 2, background: "transparent" }, children: [_jsx(CalendarGridHeader, { children: day => _jsx(CalendarHeaderCell, { style: { height: cell * 0.72, padding: 0, textAlign: "center", fontFamily: "inherit", fontSize: "inherit", fontStyle: "inherit", lineHeight: "inherit", fontWeight: controlFontWeight, opacity: controlOpacity.secondary }, children: day }) }), _jsx(CalendarGridBody, { children: date => _jsx(CalendarCell, { date: date, className: state => dimmedClass(state.isDisabled || state.isUnavailable, state.defaultClassName) ?? "", style: state => cellStyle(metrics, color, state, daySelection(state, ranged ?? single), range) }) })] })] });
19
+ return _jsxs(_Fragment, { children: [_jsxs("header", { style: { display: "grid", gridTemplateColumns: "auto 1fr auto", alignItems: "center", gap: metrics.gap }, children: [_jsx(Button, { slot: "previous", size: size, iconOnly: true, "aria-label": "Previous month", children: _jsx(Chevron, { direction: direction === "rtl" ? "next" : "previous" }) }), _jsx(CalendarHeading, { style: { margin: 0, textAlign: "center", fontFamily: "inherit", fontSize: "inherit", fontStyle: "inherit", lineHeight: "inherit", fontWeight: controlFontWeight } }), _jsx(Button, { slot: "next", size: size, iconOnly: true, "aria-label": "Next month", children: _jsx(Chevron, { direction: direction === "rtl" ? "previous" : "next" }) })] }), _jsxs(CalendarGrid, { style: { width: "max-content", margin: 0, borderCollapse: "separate", borderSpacing: 2, background: "transparent" }, children: [_jsx(CalendarGridHeader, { children: day => _jsx(CalendarHeaderCell, { style: { height: cell * 0.72, padding: 0, textAlign: "center", fontFamily: "inherit", fontSize: "inherit", fontStyle: "inherit", lineHeight: "inherit", fontWeight: controlFontWeight, opacity: controlOpacity.secondary }, children: day }) }), _jsx(CalendarGridBody, { children: date => _jsx(CalendarCell, { date: date, className: state => dimmedClass(state.isDisabled || state.isUnavailable, state.defaultClassName) ?? "", style: state => cellStyle(metrics, color, state, daySelection(state, ranged ?? single), range) }) })] })] });
20
20
  }
21
21
  export function calendarRootStyle(metrics, style) {
22
22
  return {
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { forwardRef, useCallback, useContext, useEffect, useRef } from "react";
2
+ import { Children, cloneElement, forwardRef, useCallback, useContext, useEffect, useRef } from "react";
3
+ import { ClearPressResponder } from "react-aria/private/interactions/PressResponder";
3
4
  import { MenuTrigger as AriaMenuTrigger, OverlayTriggerStateContext, Pressable as AriaPressable } from "react-aria-components";
4
5
  import { ariaOpenState } from "./control/open-state.js";
5
6
  import { Menu } from "./menu.js";
@@ -8,7 +9,12 @@ export function ContextMenuRoot({ children, ...state }) {
8
9
  return _jsx(AriaMenuTrigger, { ...ariaOpenState(state), trigger: "contextMenu", children: children });
9
10
  }
10
11
  export const ContextMenuTrigger = forwardRef(function ContextMenuTrigger({ children, disabled }, ref) {
11
- return _jsx(AriaPressable, { ref: ref, isDisabled: disabled, children: children });
12
+ // The menu's press state belongs to the trigger alone. What the trigger holds, such as the rows of a
13
+ // list, keeps its own presses instead of all showing the menu's.
14
+ const child = Children.only(children);
15
+ const held = child.props.children;
16
+ const content = held === undefined ? child : cloneElement(child, undefined, _jsx(ClearPressResponder, { children: held }));
17
+ return _jsx(AriaPressable, { ref: ref, isDisabled: disabled, children: content });
12
18
  });
13
19
  function setRef(ref, value) {
14
20
  if (typeof ref === "function")
@@ -44,9 +44,10 @@ export function controlMetrics(visual, size, radius) {
44
44
  visual,
45
45
  spacing,
46
46
  height,
47
- // Side padding follows the size, never below half the Appearance spacing,
48
- // so a compact icon-only control stays close to square.
49
- inset: Math.max(visual.spacing / 2, spacing),
47
+ // Side padding follows the size, never below two thirds of the Appearance
48
+ // spacing, so a small control with text still reads as a control. A Button
49
+ // holding only an icon is square instead (`iconOnly`), whatever its padding.
50
+ inset: Math.max(visual.spacing * 2 / 3, spacing),
50
51
  gap: Math.max(4, spacing / 2),
51
52
  indicator: Math.round(14 + spacing / 3),
52
53
  listInset: Math.max(4, spacing / 3),
package/dist/dialog.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { forwardRef } from "react";
3
3
  import { Dialog as AriaDialog, DialogTrigger as AriaDialogTrigger, Heading as AriaHeading, Modal as AriaModal, ModalOverlay as AriaModalOverlay, Text as AriaText } from "react-aria-components";
4
4
  import { Button } from "./button.js";
5
- import { controlFontSizes, controlFontWeight, controlOpacity } from "./control/control.js";
5
+ import { controlFontSizes, controlFontWeight, controlLineHeight, controlOpacity } from "./control/control.js";
6
6
  import { ariaOpenState } from "./control/open-state.js";
7
7
  import { colorOpacity, darkCanvas } from "./foundation/color.js";
8
8
  import { resolveDirection, useDirection } from "./foundation/direction.js";
@@ -90,7 +90,7 @@ export const DialogTitle = forwardRef(function DialogTitle({ style, ...propertie
90
90
  fontFamily: "inherit",
91
91
  fontSize: controlFontSizes.large,
92
92
  fontWeight: controlFontWeight,
93
- lineHeight: 1.5,
93
+ lineHeight: controlLineHeight,
94
94
  ...style
95
95
  } });
96
96
  });
@@ -98,7 +98,7 @@ export const DialogDescription = forwardRef(function DialogDescription({ style,
98
98
  return _jsx(AriaText, { ...properties, ref: ref, slot: "description", style: {
99
99
  margin: 0,
100
100
  fontSize: controlFontSizes.medium,
101
- lineHeight: 1.5,
101
+ lineHeight: controlLineHeight,
102
102
  opacity: controlOpacity.secondary,
103
103
  ...style
104
104
  } });
@@ -11,7 +11,7 @@ const DisclosureStyleContext = createContext(null);
11
11
  export const DisclosureRoot = forwardRef(function Disclosure({ children, className, color = "transparent", defaultExpanded, disabled = false, expanded, onExpandedChange, radius, size, style, ...properties }, ref) {
12
12
  const metrics = useControlMetrics(size, radius);
13
13
  const context = useMemo(() => ({ color, metrics }), [color, metrics]);
14
- return _jsx(DisclosureStyleContext.Provider, { value: context, children: _jsx(AriaDisclosure, { ...properties, ...(expanded === undefined ? {} : { isExpanded: expanded }), ...(defaultExpanded === undefined ? {} : { defaultExpanded }), ref: ref, className: className, isDisabled: disabled, onExpandedChange: onExpandedChange, style: { display: "grid", minWidth: 0, fontFamily: "inherit", fontSize: metrics.fontSize, ...style }, children: children }) });
14
+ return _jsx(DisclosureStyleContext.Provider, { value: context, children: _jsx(AriaDisclosure, { ...properties, ...(expanded === undefined ? {} : { isExpanded: expanded }), ...(defaultExpanded === undefined ? {} : { defaultExpanded }), ref: ref, className: className, isDisabled: disabled, onExpandedChange: onExpandedChange, style: { display: "grid", minWidth: 0, fontFamily: "inherit", ...style }, children: children }) });
15
15
  });
16
16
  /** The control that changes the Disclosure's expansion state. */
17
17
  export const DisclosureTrigger = forwardRef(function DisclosureTrigger({ children, className, style, ...properties }, ref) {
@@ -38,6 +38,8 @@ export const DisclosureTrigger = forwardRef(function DisclosureTrigger({ childre
38
38
  background: "none",
39
39
  cursor: "pointer",
40
40
  font: "inherit",
41
+ // Only the trigger takes the Disclosure's size: the revealed content keeps its own.
42
+ fontSize: metrics.fontSize,
41
43
  fontWeight: 500,
42
44
  lineHeight: controlLineHeight,
43
45
  textAlign: "start",
@@ -21,8 +21,10 @@ export declare function mixColor(base: string, target: string, amount: number):
21
21
  export declare function colorOpacity(value: string, opacity: number): string;
22
22
  /** Resolves one Color against the active Appearance colors. */
23
23
  export declare function resolveColor(value: Color, colors: AppearanceColors): string;
24
- /** Derives one level from a concrete base color. */
24
+ /** Derives one level from a base color. */
25
25
  export declare function colorLevel(base: string, level: ColorLevel, colors: AppearanceColors): string;
26
+ /** The perceived difference between two colors (OKLab distance), or `null` when either cannot be measured. */
27
+ export declare function perceivedDistance(first: string, second: string): number | null;
26
28
  /** Every level derived from one base color. */
27
29
  export declare function colorScale(base: string, colors: AppearanceColors): ColorScale;
28
30
  /**
@@ -6,6 +6,7 @@ import parse from "colorjs.io/src/parse.js";
6
6
  import serialize from "colorjs.io/src/serialize.js";
7
7
  import to from "colorjs.io/src/to.js";
8
8
  import toGamut from "colorjs.io/src/toGamut.js";
9
+ import deltaEOK from "colorjs.io/src/deltaE/deltaEOK.js";
9
10
  import sRGB from "colorjs.io/src/spaces/srgb.js";
10
11
  import sRGB_Linear from "colorjs.io/src/spaces/srgb-linear.js";
11
12
  import HSL from "colorjs.io/src/spaces/hsl.js";
@@ -28,12 +29,19 @@ for (const space of [sRGB, sRGB_Linear, HSL, HWB, Lab, LCH, OKLab, OKLCH, P3, A9
28
29
  * Levels move a color toward the canvas (`subtle`, `soft`) or toward the
29
30
  * content color (`strong`, `intense`). Direction therefore follows the colors
30
31
  * themselves, so every level keeps its meaning in any Theme.
32
+ *
33
+ * Each level is a perceived distance (OKLab): how far a canvas level stays from
34
+ * the canvas, and how far a content level moves from the color itself. A fixed
35
+ * share of the way would fade a light color on a light canvas and barely move a
36
+ * dark one; one distance reads the same in every Theme. A color closer to the
37
+ * canvas than a level's distance is that level already. Colors that cannot be
38
+ * measured, such as CSS variables, move by the share instead.
31
39
  */
32
40
  const levels = {
33
- subtle: { toward: "background", amount: 0.84 },
34
- soft: { toward: "background", amount: 0.62 },
35
- strong: { toward: "foreground", amount: 0.22 },
36
- intense: { toward: "foreground", amount: 0.42 }
41
+ subtle: { toward: "background", distance: 0.07, share: 0.84 },
42
+ soft: { toward: "background", distance: 0.16, share: 0.62 },
43
+ strong: { toward: "foreground", distance: 0.09, share: 0.22 },
44
+ intense: { toward: "foreground", distance: 0.17, share: 0.42 }
37
45
  };
38
46
  /*
39
47
  * Every derivation below is pure and repeats constantly across components and
@@ -128,12 +136,28 @@ export function resolveColor(value, colors) {
128
136
  cache.set(value, result);
129
137
  return result;
130
138
  }
131
- /** Derives one level from a concrete base color. */
139
+ /** Derives one level from a base color. */
132
140
  export function colorLevel(base, level, colors) {
133
141
  if (level === "base")
134
142
  return base;
135
- const { toward, amount } = levels[level];
136
- return mixColor(base, colors[toward], amount);
143
+ const { toward, distance, share } = levels[level];
144
+ const target = colors[toward];
145
+ // Mixing in OKLab moves along a straight line, so the distance covered is exactly the share of the span.
146
+ const span = perceivedDistance(base, target);
147
+ if (span === null)
148
+ return mixColor(base, target, share);
149
+ if (span === 0)
150
+ return base;
151
+ const amount = toward === "background" ? 1 - distance / span : distance / span;
152
+ return mixColor(base, target, Math.round(Math.min(1, Math.max(0, amount)) * 1_000) / 1_000);
153
+ }
154
+ /** The perceived difference between two colors (OKLab distance), or `null` when either cannot be measured. */
155
+ export function perceivedDistance(first, second) {
156
+ const a = concreteColor(first);
157
+ const b = concreteColor(second);
158
+ if (a === null || b === null)
159
+ return null;
160
+ return deltaEOK(parse(a), parse(b));
137
161
  }
138
162
  /** Every level derived from one base color. */
139
163
  export function colorScale(base, colors) {
@@ -227,7 +251,7 @@ export function recessColor(base) {
227
251
  return remember(recessCache, base, result);
228
252
  }
229
253
  /** The constant difference between a recess and the paint it sinks into. */
230
- const recessContrast = 1.085;
254
+ const recessContrast = 1.15;
231
255
  /** The lighter Appearance content candidate: the paint of anything lit from above, such as a thumb. */
232
256
  export function lightColor(colors) {
233
257
  return darkCanvas(colors) ? colors.foreground : colors.default;
package/dist/popover.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { forwardRef } from "react";
3
3
  import { Dialog as AriaDialog, DialogTrigger as AriaDialogTrigger, Heading as AriaHeading, Popover as AriaPopover } from "react-aria-components";
4
4
  import { Button } from "./button.js";
5
- import { controlFontSizes, controlFontWeight } from "./control/control.js";
5
+ import { controlFontSizes, controlFontWeight, controlLineHeight } from "./control/control.js";
6
6
  import { ariaOpenState } from "./control/open-state.js";
7
7
  import { resolveDirection, useDirection } from "./foundation/direction.js";
8
8
  import MotionStyle, { overlayMotionClass, overlayTransition } from "./foundation/motion-style.js";
@@ -45,7 +45,7 @@ export const PopoverTitle = forwardRef(function PopoverTitle({ style, ...propert
45
45
  fontFamily: "inherit",
46
46
  fontSize: controlFontSizes.medium,
47
47
  fontWeight: controlFontWeight,
48
- lineHeight: 1.5,
48
+ lineHeight: controlLineHeight,
49
49
  ...style
50
50
  } });
51
51
  });
package/dist/snippet.js CHANGED
@@ -34,7 +34,7 @@ export const Snippet = forwardRef(function Snippet({ children, code = false, cop
34
34
  lineHeight: 1.6,
35
35
  fontFamily: code ? "ui-monospace, SFMono-Regular, Menlo, monospace" : "inherit",
36
36
  fontSize: code ? "0.9em" : undefined
37
- }, children: children }), _jsx(Button, { size: "small", shadow: false, "aria-label": copied ? "Copied" : copyLabel, style: { position: "absolute", insetBlockStart: padding / 2, insetInlineEnd: padding / 2, width: metrics.height, paddingInline: 0 }, onPress: () => void copyText(children).then(setCopied), children: copied ? _jsx(Check, { ...iconProps(14) }) : _jsx(Copy, { ...iconProps(14) }) })] });
37
+ }, children: children }), _jsx(Button, { size: "small", shadow: false, "aria-label": copied ? "Copied" : copyLabel, iconOnly: true, style: { position: "absolute", insetBlockStart: padding / 2, insetInlineEnd: padding / 2 }, onPress: () => void copyText(children).then(setCopied), children: copied ? _jsx(Check, { ...iconProps(14) }) : _jsx(Copy, { ...iconProps(14) }) })] });
38
38
  });
39
39
  /**
40
40
  * Writes text to the clipboard and reports whether it could. A Program's frame
@@ -14,8 +14,10 @@ export type { ShadowOptions } from "./shadow-options.js";
14
14
  * flat — a region level with its surroundings: only a boundary.
15
15
  * recessed — something that holds content: a slightly deeper paint and an
16
16
  * inner shadow instead of an outer one.
17
+ * none — part of its surroundings: nothing painted at rest; hover, press,
18
+ * and focus still show.
17
19
  */
18
- export type SurfaceDepth = "raised" | "flat" | "recessed";
20
+ export type SurfaceDepth = "raised" | "flat" | "recessed" | "none";
19
21
  export interface SurfaceOwnProps extends MaterialOverrides, ShadowOverrides, RadiusProps {
20
22
  readonly color?: Color;
21
23
  readonly depth?: SurfaceDepth;
@@ -79,9 +79,12 @@ export function surfacePaint(visual, color, depth, options, shadowOptions, inter
79
79
  // A recess moves away from its surroundings toward whichever extreme has
80
80
  // room, by one constant perceptual difference on any canvas.
81
81
  const rest = depth === "recessed" ? recessColor(base) : base;
82
- // A transparent Surface has no substance of its own: it takes the content
83
- // color of whatever it sits on and only reveals interaction as a veil.
84
- const clear = base === "transparent";
82
+ // A clear Surface, with no depth or a transparent color, has no substance of
83
+ // its own: it paints nothing at rest, takes the content color of whatever it
84
+ // sits on, and only reveals interaction as a veil. With no depth, a color
85
+ // other than the neutral one colors its content instead of a fill.
86
+ const clear = depth === "none" || base === "transparent";
87
+ const tinted = depth === "none" && base !== "transparent" && base !== colors.default;
85
88
  const fill = clear
86
89
  ? shift > 0 ? colorOpacity(colors.foreground, shift * clearVeil) : "transparent"
87
90
  : mixColor(rest, colors.foreground, shift);
@@ -131,7 +134,7 @@ export function surfacePaint(visual, color, depth, options, shadowOptions, inter
131
134
  fill,
132
135
  // The label is chosen once for the resting paint: interaction shades stay
133
136
  // close to it, and re-deciding per state would flip text on near-ties.
134
- text: clear ? "inherit" : readableColor(rest, colors),
137
+ text: tinted ? base : clear ? "inherit" : readableColor(rest, colors),
135
138
  ring: colorOpacity(ringBase, 0.34),
136
139
  shadow: [hairline, outer === "none" ? null : outer].filter(Boolean).join(", ") || "none",
137
140
  distortion: level >= materialLevel.full && translucent ? material.distortion : 0,
@@ -188,6 +191,11 @@ const edgeLightEnds = Object.freeze({
188
191
  recessed: {
189
192
  light: { edge: 0.45, side: 0.12, spill: 0, hairline: 0.12 },
190
193
  dark: { edge: 0.12, side: 0.04, spill: 0, hairline: 0.45 }
194
+ },
195
+ // Without depth nothing is edged; the ends only complete the table.
196
+ none: {
197
+ light: { edge: 0, side: 0, spill: 0, hairline: 0 },
198
+ dark: { edge: 0, side: 0, spill: 0, hairline: 0 }
191
199
  }
192
200
  });
193
201
  /** A separate compositor pass for refraction, present only when it is visible. */
package/dist/tooltip.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { forwardRef } from "react";
3
3
  import { Tooltip as AriaTooltip, TooltipTrigger as AriaTooltipTrigger } from "react-aria-components";
4
4
  import { Button } from "./button.js";
5
- import { controlFontSizes } from "./control/control.js";
5
+ import { controlFontSizes, controlLineHeight } from "./control/control.js";
6
6
  import { ariaOpenState } from "./control/open-state.js";
7
7
  import { resolveDirection, useDirection } from "./foundation/direction.js";
8
8
  import MotionStyle, { overlayMotionClass, overlayTransition } from "./foundation/motion-style.js";
@@ -29,7 +29,7 @@ export const TooltipContent = forwardRef(function TooltipContent({ children, cla
29
29
  paddingInline: inset * 1.5,
30
30
  outline: "none",
31
31
  fontSize: controlFontSizes.small,
32
- lineHeight: 1.4,
32
+ lineHeight: controlLineHeight,
33
33
  ...style
34
34
  }, children: children }) })] });
35
35
  });
package/dist/window.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createContext, forwardRef, useContext } from "react";
3
3
  import { Button } from "./button.js";
4
- import { transition } from "./control/control.js";
4
+ import { controlFontSizes, transition } from "./control/control.js";
5
5
  import { resolveColor } from "./foundation/color.js";
6
6
  import { headerHeight } from "./foundation/layout.js";
7
7
  import { scale } from "./foundation/scale.js";
@@ -100,7 +100,7 @@ const WindowIdentity = forwardRef(function WindowIdentity({ icon, title, style,
100
100
  overflow: "hidden",
101
101
  textOverflow: "ellipsis",
102
102
  whiteSpace: "nowrap",
103
- fontSize: "0.8125em",
103
+ fontSize: controlFontSizes.medium,
104
104
  fontWeight: 500
105
105
  }, children: title })] });
106
106
  });
@@ -127,15 +127,15 @@ const WindowAction = forwardRef(function WindowAction({ ...properties }, ref) {
127
127
  return _jsx(Button, { ...properties, ref: ref, size: "xsmall" });
128
128
  });
129
129
  const WindowMinimize = forwardRef(function WindowMinimize({ "aria-label": label = "Minimize", preventFocusOnPress = true, ...properties }, ref) {
130
- return _jsx(WindowAction, { ...properties, ref: ref, "aria-label": label, preventFocusOnPress: preventFocusOnPress, children: _jsx(ArrowDownLeft, { ...iconProps(14) }) });
130
+ return _jsx(WindowAction, { ...properties, ref: ref, iconOnly: true, "aria-label": label, preventFocusOnPress: preventFocusOnPress, children: _jsx(ArrowDownLeft, { ...iconProps(14) }) });
131
131
  });
132
132
  const WindowMaximize = forwardRef(function WindowMaximize({ "aria-label": label, maximized, onPress, ...properties }, ref) {
133
133
  const header = useHeader();
134
134
  const selected = maximized ?? header.maximized;
135
- return _jsx(WindowAction, { ...properties, ref: ref, onPress: onPress ?? header.onMaximize, "aria-label": label ?? (selected ? "Restore" : "Maximize"), children: selected ? _jsx(Minimize2, { ...iconProps(14) }) : _jsx(Maximize2, { ...iconProps(14) }) });
135
+ return _jsx(WindowAction, { ...properties, ref: ref, iconOnly: true, onPress: onPress ?? header.onMaximize, "aria-label": label ?? (selected ? "Restore" : "Maximize"), children: selected ? _jsx(Minimize2, { ...iconProps(14) }) : _jsx(Maximize2, { ...iconProps(14) }) });
136
136
  });
137
137
  const WindowClose = forwardRef(function WindowClose({ "aria-label": label = "Close", color = "danger", preventFocusOnPress = true, ...properties }, ref) {
138
- return _jsx(WindowAction, { ...properties, ref: ref, "aria-label": label, color: color, preventFocusOnPress: preventFocusOnPress, children: _jsx(X, { ...iconProps(14) }) });
138
+ return _jsx(WindowAction, { ...properties, ref: ref, iconOnly: true, "aria-label": label, color: color, preventFocusOnPress: preventFocusOnPress, children: _jsx(X, { ...iconProps(14) }) });
139
139
  });
140
140
  const Header = Object.assign(HeaderRoot, {
141
141
  Identity: WindowIdentity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/react-ui",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
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.63",
60
+ "@phreshos/core": "^0.1.65",
61
61
  "colorjs.io": "^0.7.1",
62
62
  "lucide-react": "^1.48.0",
63
63
  "react-aria": "^3.51.0",