@phreshos/react-ui 0.1.25 → 0.1.27

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.
Files changed (65) hide show
  1. package/README.md +76 -23
  2. package/dist/alert-dialog.d.ts +23 -0
  3. package/dist/alert-dialog.js +25 -0
  4. package/dist/button.d.ts +2 -1
  5. package/dist/button.js +2 -2
  6. package/dist/checkbox.d.ts +2 -1
  7. package/dist/checkbox.js +2 -2
  8. package/dist/color.js +1 -1
  9. package/dist/context-menu.d.ts +21 -0
  10. package/dist/context-menu.js +18 -0
  11. package/dist/control-surface.d.ts +6 -7
  12. package/dist/control-surface.js +4 -4
  13. package/dist/control.js +1 -1
  14. package/dist/dialog.d.ts +46 -0
  15. package/dist/dialog.js +91 -0
  16. package/dist/direction.d.ts +9 -0
  17. package/dist/direction.js +31 -0
  18. package/dist/dropdown-menu.d.ts +21 -0
  19. package/dist/dropdown-menu.js +19 -0
  20. package/dist/flex.js +1 -1
  21. package/dist/grid.js +1 -1
  22. package/dist/input.js +2 -2
  23. package/dist/main.d.ts +11 -3
  24. package/dist/main.js +9 -2
  25. package/dist/material-options.d.ts +2 -1
  26. package/dist/material-options.js +1 -1
  27. package/dist/menu.d.ts +35 -0
  28. package/dist/menu.js +85 -0
  29. package/dist/motion-style.d.ts +1 -0
  30. package/dist/motion-style.js +15 -7
  31. package/dist/overlay-placement.d.ts +4 -0
  32. package/dist/overlay-placement.js +6 -0
  33. package/dist/panel.d.ts +17 -8
  34. package/dist/panel.js +36 -21
  35. package/dist/popover.d.ts +30 -0
  36. package/dist/popover.js +46 -0
  37. package/dist/radio.d.ts +14 -7
  38. package/dist/radio.js +36 -11
  39. package/dist/radius.d.ts +2 -2
  40. package/dist/radius.js +3 -1
  41. package/dist/scroll-area.js +6 -2
  42. package/dist/select.d.ts +2 -1
  43. package/dist/select.js +21 -3
  44. package/dist/shadow-options.d.ts +25 -0
  45. package/dist/shadow-options.js +25 -0
  46. package/dist/slider.d.ts +13 -3
  47. package/dist/slider.js +62 -30
  48. package/dist/spacing.js +1 -1
  49. package/dist/surface-edge.d.ts +0 -1
  50. package/dist/surface-edge.js +46 -49
  51. package/dist/surface.d.ts +5 -1
  52. package/dist/surface.js +11 -6
  53. package/dist/switch.d.ts +2 -1
  54. package/dist/switch.js +4 -2
  55. package/dist/text-control.d.ts +2 -1
  56. package/dist/textarea.js +2 -2
  57. package/dist/toggle-indicator.d.ts +6 -4
  58. package/dist/toggle-indicator.js +3 -5
  59. package/dist/tooltip.d.ts +21 -0
  60. package/dist/tooltip.js +38 -0
  61. package/dist/ui-provider.d.ts +14 -0
  62. package/dist/ui-provider.js +21 -0
  63. package/package.json +1 -1
  64. package/dist/appearance-provider.d.ts +0 -10
  65. package/dist/appearance-provider.js +0 -13
package/README.md CHANGED
@@ -38,22 +38,40 @@ import { Button, Surface } from "@phreshos/react-ui"
38
38
  ```
39
39
 
40
40
  Without a provider, components use Core's `defaultAppearance` and reactively
41
- follow the browser's complete visual preferences. `AppearanceProvider` can replace
42
- Appearance or Preferences for a subtree; omitted values inherit from the nearest provider.
41
+ follow the browser's complete visual preferences. `UIProvider` supplies the
42
+ Appearance, Preferences, and direction explicitly established for a React UI
43
+ subtree. Each omitted property inherits independently from the nearest provider
44
+ or follows its own default resolution. A provider with no properties introduces
45
+ no boundary or behavior.
43
46
  React UI also exports that same canonical `defaultAppearance` value for callers
44
47
  that need it explicitly.
45
48
  See [Appearance](https://docs.phreshos.com/system/appearance) for the contract
46
49
  interpreted by the provider and components.
47
50
 
51
+ React UI is neutral between left-to-right and right-to-left interfaces.
52
+ An explicit `UIProvider` direction creates a boundary that participates in
53
+ native DOM inheritance without creating a layout box. When direction is
54
+ omitted, the provider leaves DOM inheritance unchanged. React UI reads the
55
+ nearest explicit direction—or `<html dir>` when there is none—only for
56
+ JavaScript behavior and portals that cannot rely on native inheritance.
57
+
58
+ ```tsx
59
+ import { UIProvider, Select } from "@phreshos/react-ui"
60
+
61
+ <UIProvider direction="rtl">
62
+ <Select label="Choice" options={[{ value: "one", label: "One" }]} />
63
+ </UIProvider>
64
+ ```
65
+
48
66
  ```tsx
49
- import { AppearanceProvider, Button, useBrowserPreferences } from "@phreshos/react-ui"
67
+ import { UIProvider, Button, useBrowserPreferences } from "@phreshos/react-ui"
50
68
 
51
69
  function Example() {
52
70
  const browser = useBrowserPreferences()
53
71
 
54
- return <AppearanceProvider preferences={{ ...browser, theme: "dark" }}>
72
+ return <UIProvider preferences={{ ...browser, theme: "dark" }}>
55
73
  <Button>Dark subtree</Button>
56
- </AppearanceProvider>
74
+ </UIProvider>
57
75
  }
58
76
  ```
59
77
 
@@ -75,12 +93,11 @@ spacing and radius follow Appearance. `disabled` prevents activation and focus;
75
93
  <Button color="danger:soft" size="small">Delete</Button>
76
94
  ```
77
95
 
78
- `Surface` is the material-owning element. It renders a `div` by default, while
96
+ `Surface` is the material- and shadow-owning element. It renders a `div` by default, while
79
97
  `as` selects another React element and preserves that element's native properties
80
98
  and ref type. Surface owns its paint, opacity, frost, refraction, grain, edge,
81
- radius, and clipping requirements. It never creates or consumes a shadow; shadow
82
- remains an independent visual concern. `radius` accepts a size level, a number in
83
- pixels, or a CSS radius and defaults to `medium`.
99
+ outer shadow, radius, and clipping requirements. `radius` accepts a size level, `full`, a
100
+ number in pixels, or a CSS radius and defaults to `medium`.
84
101
 
85
102
  ```tsx
86
103
  <Surface color="background:soft" radius="large">Derived values</Surface>
@@ -89,27 +106,35 @@ pixels, or a CSS radius and defaults to `medium`.
89
106
  ```
90
107
 
91
108
  `as` can also select an outside React component. A valid Surface host preserves
92
- the `style` and `children` it receives on one host element and forwards its ref
93
- to that same element. This lets layout components carry the material without a
94
- wrapper. The host retains ownership of its own behavior and layout properties;
95
- Surface retains ownership of material, edge, radius, and required geometry.
109
+ the `style`, `dir`, and `children` it receives on one host element and forwards
110
+ its ref to that same element. This lets layout components carry the material
111
+ without a wrapper. The host retains ownership of its own behavior and layout
112
+ properties; Surface retains ownership of material, edge, shadow, radius, and
113
+ required geometry.
96
114
 
97
115
  `MaterialOptions` defines `opacity`, `backdrop`, `grain`, `grainAmount`,
98
116
  `distortion`, and `saturation`. Every material-bearing component, including
99
117
  Surface, exposes these values through its `material` prop. Color remains a
100
- separate property. Omitted material values follow
101
- `appearance.material`. Effect options accept a scale level or a direct number;
118
+ separate property. Omission or `true` uses `appearance.material`; `false`
119
+ removes the material and paints the resolved color as a normal background.
120
+ Effect options accept a scale level or a direct number;
102
121
  opacity affects Surface paint only, never its content. The material edge is part
103
122
  of the same Surface rather than a second public entity.
104
123
 
105
124
  Surface-based controls expose the same separate `color` and `material` props.
125
+ `ShadowOptions` similarly groups `x`, `y`, `blur`, `spread`, and `opacity` under
126
+ the `shadow` prop. Omission or `true` follows the active `appearance.shadow`
127
+ branch, while `false` removes the shadow. Each option accepts a scale level or
128
+ direct number. The shadow is a neutral black
129
+ outer shadow. Surface-based controls expose the same `shadow` prop.
106
130
  For text fields and Select, `material` targets the field or trigger; for Checkbox,
107
131
  Switch, and Radio, it targets the indicator. RadioGroup supplies material defaults
108
- to its options, and a Radio can override them.
132
+ to its options, and a Radio can override them. Shadow follows the same targets and
133
+ inheritance path.
109
134
 
110
135
  ```tsx
111
136
  <Button color="primary:base" material={{ opacity: 0.6, backdrop: 0 }}>Save</Button>
112
- <Input label="Name" radius="large" material={{ grain: "small" }} />
137
+ <Input label="Name" radius="large" material={{ grain: "small" }} shadow={{ blur: "small" }} />
113
138
  ```
114
139
 
115
140
  `Panel` composes an outer `Surface`, an optional header, and an inset content
@@ -119,13 +144,41 @@ Appearance spacing. Positioning, modality, and lifecycle belong to the caller.
119
144
  ```tsx
120
145
  import { Panel } from "@phreshos/react-ui"
121
146
 
122
- <Panel header={<h2>Title</h2>} contentProps={{ style: { padding: 16 } }}>
123
- Content
147
+ <Panel>
148
+ <Panel.Header><h2>Title</h2></Panel.Header>
149
+ <Panel.Content style={{ padding: 16 }}>Content</Panel.Content>
124
150
  </Panel>
125
151
  ```
126
152
 
127
- Native properties and the forwarded ref target the outer Surface.
128
- `contentProps` targets the inner Surface; `children` supplies its content.
153
+ Native properties and the root ref target the outer Surface. `Panel.Header`
154
+ owns the optional leading region, while `Panel.Content` is the independently
155
+ configurable inner Surface.
156
+
157
+ ## Overlays
158
+
159
+ Overlay components expose each behavioral role as a named part. Roots own open
160
+ state, triggers own activation, positioned content owns its Surface, and the
161
+ semantic content remains explicit.
162
+
163
+ ```tsx
164
+ <DropdownMenu>
165
+ <DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
166
+ <DropdownMenu.Content>
167
+ <Menu aria-label="Document actions">
168
+ <Menu.Item onAction={open}>Open</Menu.Item>
169
+ <Menu.Item color="danger:base" onAction={remove}>Delete</Menu.Item>
170
+ </Menu>
171
+ </DropdownMenu.Content>
172
+ </DropdownMenu>
173
+ ```
174
+
175
+ `Popover` presents anchored non-modal content. `DropdownMenu` and
176
+ `ContextMenu` open the same `Menu` contract through different interactions.
177
+ `Dialog` blocks interaction behind it and may be dismissable, while
178
+ `AlertDialog` requires an explicit decision by default. `Tooltip` supplies a
179
+ short description on focus or hover. Portal cleanup, focus restoration,
180
+ keyboard behavior, nested ownership, and entrance and exit transactions remain
181
+ component-owned.
129
182
 
130
183
  ## Inputs
131
184
 
@@ -143,8 +196,8 @@ Server SDK.
143
196
  Fields distinguish hover, pointer focus, keyboard focus, and invalid state.
144
197
  Shared CSS transitions use `appearance.transaction` for colors and corner
145
198
  radius. The material fill and opacity values transition on the painted layers, not on the
146
- Surface host. Select menus combine a small placement-aware slide with an overlay
147
- opacity fade for entry and exit, using React Aria's animation lifecycle. Preferences with animations disabled make
199
+ Surface host. Select menus scale from `1.05` to `1` while fading in, and reverse
200
+ that motion when exiting, using React Aria's animation lifecycle. Preferences with animations disabled make
148
201
  these changes immediate without changing the Appearance value.
149
202
  Blur, distortion, geometry, and Surface host opacity are not transitioned; gradients
150
203
  and structurally removed effects change directly rather than adding extra
@@ -0,0 +1,23 @@
1
+ import type { ComponentProps } from "react";
2
+ import { DialogRoot } from "./dialog.js";
3
+ import type { DialogBackdropProps, DialogContentProps } from "./dialog.js";
4
+ export type AlertDialogRootProps = ComponentProps<typeof DialogRoot>;
5
+ export declare function AlertDialogRoot(properties: AlertDialogRootProps): import("react").JSX.Element;
6
+ export type AlertDialogBackdropProps = DialogBackdropProps;
7
+ export declare const AlertDialogBackdrop: import("react").ForwardRefExoticComponent<DialogBackdropProps & import("react").RefAttributes<HTMLDivElement>>;
8
+ export type AlertDialogContentProps = Omit<DialogContentProps, "role">;
9
+ export declare const AlertDialogContent: import("react").ForwardRefExoticComponent<AlertDialogContentProps & import("react").RefAttributes<HTMLElement>>;
10
+ /** A dialog requiring an explicit decision rather than ambient dismissal. */
11
+ export declare const AlertDialog: typeof AlertDialogRoot & {
12
+ Root: typeof AlertDialogRoot;
13
+ Trigger: import("react").ForwardRefExoticComponent<import("./button.js").ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
14
+ Backdrop: import("react").ForwardRefExoticComponent<DialogBackdropProps & import("react").RefAttributes<HTMLDivElement>>;
15
+ Content: import("react").ForwardRefExoticComponent<AlertDialogContentProps & import("react").RefAttributes<HTMLElement>>;
16
+ Header: import("react").ForwardRefExoticComponent<import("./dialog.js").DialogHeaderProps & import("react").RefAttributes<HTMLDivElement>>;
17
+ Title: import("react").ForwardRefExoticComponent<import("react-aria-components").HeadingProps & import("react").RefAttributes<HTMLHeadingElement>>;
18
+ Description: import("react").ForwardRefExoticComponent<import("react-aria-components").TextProps & import("react").RefAttributes<HTMLElement>>;
19
+ Body: import("react").ForwardRefExoticComponent<import("./dialog.js").DialogBodyProps & import("react").RefAttributes<HTMLDivElement>>;
20
+ Footer: import("react").ForwardRefExoticComponent<import("./dialog.js").DialogFooterProps & import("react").RefAttributes<HTMLDivElement>>;
21
+ Close: import("react").ForwardRefExoticComponent<import("./button.js").ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
22
+ };
23
+ export type AlertDialogProps = ComponentProps<typeof AlertDialogRoot>;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { DialogBackdrop, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "./dialog.js";
4
+ export function AlertDialogRoot(properties) {
5
+ return _jsx(DialogRoot, { ...properties });
6
+ }
7
+ export const AlertDialogBackdrop = forwardRef(function AlertDialogBackdrop(properties, ref) {
8
+ return _jsx(DialogBackdrop, { ...properties, ref: ref, isDismissable: properties.isDismissable ?? false, isKeyboardDismissDisabled: properties.isKeyboardDismissDisabled ?? true });
9
+ });
10
+ export const AlertDialogContent = forwardRef(function AlertDialogContent(properties, ref) {
11
+ return _jsx(DialogContent, { ...properties, ref: ref, role: "alertdialog" });
12
+ });
13
+ /** A dialog requiring an explicit decision rather than ambient dismissal. */
14
+ export const AlertDialog = Object.assign(AlertDialogRoot, {
15
+ Root: AlertDialogRoot,
16
+ Trigger: DialogTrigger,
17
+ Backdrop: AlertDialogBackdrop,
18
+ Content: AlertDialogContent,
19
+ Header: DialogHeader,
20
+ Title: DialogTitle,
21
+ Description: DialogDescription,
22
+ Body: DialogBody,
23
+ Footer: DialogFooter,
24
+ Close: DialogClose
25
+ });
package/dist/button.d.ts CHANGED
@@ -4,11 +4,12 @@ import type { ScaleLevel } from "./scale.js";
4
4
  import type { RadiusProps } from "./radius.js";
5
5
  import { type ControlColor } from "./control.js";
6
6
  import type { MaterialOverrides } from "./material-options.js";
7
+ import type { ShadowOverrides } from "./shadow-options.js";
7
8
  type NativeButtonProps = Omit<AriaButtonProps, "children" | "className" | "color" | "isDisabled" | "isPending" | "onClick" | "onPress" | "style">;
8
9
  /** A semantic color from Appearance; omission uses `default:base`. */
9
10
  export type ButtonColor = ControlColor;
10
11
  /** Properties accepted by the shared interactive button. */
11
- export interface ButtonProps extends NativeButtonProps, RadiusProps, MaterialOverrides {
12
+ export interface ButtonProps extends NativeButtonProps, RadiusProps, MaterialOverrides, ShadowOverrides {
12
13
  /** Visible Button content. */
13
14
  readonly children?: ReactNode;
14
15
  /** Base color for the material. Omission keeps the Button neutral. */
package/dist/button.js CHANGED
@@ -4,9 +4,9 @@ import { Button as AriaButton } from "react-aria-components";
4
4
  import { controlFontSizes, useControlTheme } from "./control.js";
5
5
  import { SurfaceButton } from "./control-surface.js";
6
6
  /** An Appearance-aware action with normalized pointer and keyboard behavior. */
7
- export const Button = forwardRef(function Button({ children, color, disabled = false, pending = false, onPress, radius = "medium", size = "medium", style, material, type = "button", ...properties }, ref) {
7
+ export const Button = forwardRef(function Button({ children, color, disabled = false, pending = false, onPress, radius = "medium", size = "medium", style, material, shadow, type = "button", ...properties }, ref) {
8
8
  const theme = useControlTheme({ color, radius, size });
9
- return _jsx(AriaButton, { ...properties, ref: ref, type: type, isDisabled: disabled, isPending: pending, onPress: onPress, render: (native, state) => _jsx(SurfaceButton, { native: native, material: material, paint: buttonPaint(theme, !disabled && !pending, state.isHovered, state.isPressed) }), style: ({ isFocusVisible, isHovered, isPressed }) => buttonStyle({
9
+ return _jsx(AriaButton, { ...properties, ref: ref, type: type, isDisabled: disabled, isPending: pending, onPress: onPress, render: (native, state) => _jsx(SurfaceButton, { native: native, material: material, shadow: shadow, paint: buttonPaint(theme, !disabled && !pending, state.isHovered, state.isPressed) }), style: ({ isFocusVisible, isHovered, isPressed }) => buttonStyle({
10
10
  theme,
11
11
  disabled,
12
12
  isFocusVisible,
@@ -1,7 +1,8 @@
1
1
  import type { CheckboxFieldProps } from "react-aria-components";
2
2
  import type { ControlOverrides, ControlProps, FieldProps } from "./control.js";
3
3
  import type { MaterialOverrides } from "./material-options.js";
4
- export interface CheckboxProps extends Omit<CheckboxFieldProps, ControlOverrides | "isReadOnly" | "isSelected" | "defaultSelected" | "isIndeterminate">, ControlProps, FieldProps, MaterialOverrides {
4
+ import type { ShadowOverrides } from "./shadow-options.js";
5
+ export interface CheckboxProps extends Omit<CheckboxFieldProps, ControlOverrides | "isReadOnly" | "isSelected" | "defaultSelected" | "isIndeterminate">, ControlProps, FieldProps, MaterialOverrides, ShadowOverrides {
5
6
  readonly checked?: boolean;
6
7
  readonly defaultChecked?: boolean;
7
8
  readonly indeterminate?: boolean;
package/dist/checkbox.js CHANGED
@@ -4,7 +4,7 @@ import { CheckboxField, CheckboxButton } from "react-aria-components";
4
4
  import { FieldFeedback, fieldStyle, useControlTheme } from "./control.js";
5
5
  import { ToggleIndicator, toggleStyle } from "./toggle-indicator.js";
6
6
  /** An independent boolean field with optional mixed-state presentation. */
7
- export const Checkbox = forwardRef(function Checkbox({ label, description, errorMessage, disabled, required, invalid, readOnly, checked, defaultChecked, indeterminate, size, color, style, material, ...properties }, ref) {
7
+ export const Checkbox = forwardRef(function Checkbox({ label, description, errorMessage, disabled, required, invalid, readOnly, checked, defaultChecked, indeterminate, size, color, style, material, shadow, ...properties }, ref) {
8
8
  const theme = useControlTheme({ size, color });
9
- return _jsxs(CheckboxField, { ...properties, ref: ref, isDisabled: disabled, isRequired: required, isInvalid: invalid, isReadOnly: readOnly, isSelected: checked, defaultSelected: defaultChecked, isIndeterminate: indeterminate, style: state => fieldStyle(theme, state.isDisabled, style), children: [_jsx(CheckboxButton, { style: state => toggleStyle(theme, state.isDisabled, state.isReadOnly), children: state => _jsxs(_Fragment, { children: [_jsx(ToggleIndicator, { kind: "checkbox", material: material, theme: theme, selected: state.isSelected, indeterminate: state.isIndeterminate, focused: state.isFocusVisible, invalid: state.isInvalid, hovered: !state.isDisabled && !state.isReadOnly && state.isHovered, pressed: !state.isDisabled && !state.isReadOnly && state.isPressed }), label] }) }), _jsx(FieldFeedback, { theme: theme, description: description, errorMessage: errorMessage })] });
9
+ return _jsxs(CheckboxField, { ...properties, ref: ref, isDisabled: disabled, isRequired: required, isInvalid: invalid, isReadOnly: readOnly, isSelected: checked, defaultSelected: defaultChecked, isIndeterminate: indeterminate, style: state => fieldStyle(theme, state.isDisabled, style), children: [_jsx(CheckboxButton, { style: state => toggleStyle(theme, state.isDisabled, state.isReadOnly), children: state => _jsxs(_Fragment, { children: [_jsx(ToggleIndicator, { kind: "checkbox", material: material, shadow: shadow, theme: theme, selected: state.isSelected, indeterminate: state.isIndeterminate, focused: state.isFocusVisible, invalid: state.isInvalid, hovered: !state.isDisabled && !state.isReadOnly && state.isHovered, pressed: !state.isDisabled && !state.isReadOnly && state.isPressed }), label] }) }), _jsx(FieldFeedback, { theme: theme, description: description, errorMessage: errorMessage })] });
10
10
  });
package/dist/color.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from "react";
2
2
  import { ColorSpace, mix, parse, serialize, to, toGamut, contrastWCAG21, sRGB, sRGB_Linear, HSL, HWB, Lab, LCH, OKLab, OKLCH, P3, A98RGB, ProPhoto, REC_2020, XYZ_D50, XYZ_D65 } from "colorjs.io/fn";
3
- import { useAppearance, useThemedValue } from "./appearance-provider.js";
3
+ import { useAppearance, useThemedValue } from "./ui-provider.js";
4
4
  // Register the CSS color spaces, without bundling unrelated color-model APIs.
5
5
  for (const space of [sRGB, sRGB_Linear, HSL, HWB, Lab, LCH, OKLab, OKLCH, P3, A98RGB, ProPhoto, REC_2020, XYZ_D50, XYZ_D65])
6
6
  ColorSpace.register(space);
@@ -0,0 +1,21 @@
1
+ import type { ComponentProps } from "react";
2
+ import { Pressable as AriaPressable } from "react-aria-components";
3
+ import type { MenuTriggerProps as AriaMenuTriggerProps } from "react-aria-components";
4
+ export type ContextMenuRootProps = Omit<AriaMenuTriggerProps, "trigger">;
5
+ export declare function ContextMenuRoot(properties: ContextMenuRootProps): import("react").JSX.Element;
6
+ export type ContextMenuTriggerProps = ComponentProps<typeof AriaPressable>;
7
+ export declare const ContextMenuTrigger: import("react").ForwardRefExoticComponent<Omit<import("react-aria/Pressable").PressableProps & import("react").RefAttributes<import("@react-types/shared").FocusableElement>, "ref"> & import("react").RefAttributes<import("@react-types/shared").FocusableElement>>;
8
+ /** A context-requested overlay sharing the same Menu contract as DropdownMenu. */
9
+ export declare const ContextMenu: typeof ContextMenuRoot & {
10
+ Root: typeof ContextMenuRoot;
11
+ Trigger: import("react").ForwardRefExoticComponent<Omit<import("react-aria/Pressable").PressableProps & import("react").RefAttributes<import("@react-types/shared").FocusableElement>, "ref"> & import("react").RefAttributes<import("@react-types/shared").FocusableElement>>;
12
+ Content: import("react").ForwardRefExoticComponent<import("./popover.js").PopoverContentProps & import("react").RefAttributes<HTMLElement>>;
13
+ Menu: import("./menu.js").MenuRootComponent & {
14
+ Root: import("./menu.js").MenuRootComponent;
15
+ Item: import("./menu.js").MenuItemComponent;
16
+ Section: <T>(props: import("react-aria-components").MenuSectionProps<T> & React.RefAttributes<HTMLElement>) => import("react").ReactElement<unknown, string | React.JSXElementConstructor<any>> | null;
17
+ Header: import("react").ForwardRefExoticComponent<import("react-aria-components").HeaderProps & import("react").RefAttributes<HTMLElement>>;
18
+ Separator: import("react").ForwardRefExoticComponent<import("react-aria-components").SeparatorProps & import("react").RefAttributes<HTMLElement>>;
19
+ };
20
+ };
21
+ export type ContextMenuProps = ComponentProps<typeof ContextMenuRoot>;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { MenuTrigger as AriaMenuTrigger, Pressable as AriaPressable } from "react-aria-components";
4
+ import { Menu } from "./menu.js";
5
+ import { PopoverContent } from "./popover.js";
6
+ export function ContextMenuRoot(properties) {
7
+ return _jsx(AriaMenuTrigger, { ...properties, trigger: "contextMenu" });
8
+ }
9
+ export const ContextMenuTrigger = forwardRef(function ContextMenuTrigger(properties, ref) {
10
+ return _jsx(AriaPressable, { ...properties, ref: ref });
11
+ });
12
+ /** A context-requested overlay sharing the same Menu contract as DropdownMenu. */
13
+ export const ContextMenu = Object.assign(ContextMenuRoot, {
14
+ Root: ContextMenuRoot,
15
+ Trigger: ContextMenuTrigger,
16
+ Content: PopoverContent,
17
+ Menu
18
+ });
@@ -1,20 +1,19 @@
1
1
  import type { ComponentPropsWithRef, CSSProperties, ReactNode } from "react";
2
- import type { MaterialOptions } from "./material-options.js";
2
+ import type { MaterialOverrides } from "./material-options.js";
3
+ import type { ShadowOverrides } from "./shadow-options.js";
3
4
  type Paint = Readonly<{
4
5
  background: string;
5
6
  color: string;
6
7
  }>;
7
8
  /** A native button uses Surface without changing its native contract. */
8
- export declare function SurfaceButton({ native, paint, material: options }: {
9
+ export declare function SurfaceButton({ native, paint, material: options, shadow }: {
9
10
  readonly native: ComponentPropsWithRef<"button">;
10
11
  readonly paint: Paint;
11
- readonly material?: MaterialOptions;
12
- }): import("react").JSX.Element;
12
+ } & MaterialOverrides & ShadowOverrides): import("react").JSX.Element;
13
13
  /** Void text controls use a material host while retaining native sizing. */
14
- export declare function SurfaceField({ paint, radius, children, material: options }: Readonly<{
14
+ export declare function SurfaceField({ paint, radius, children, material: options, shadow }: Readonly<{
15
15
  paint: Paint;
16
16
  radius: CSSProperties["borderRadius"];
17
17
  children: ReactNode;
18
- material?: MaterialOptions;
19
- }>): import("react").JSX.Element;
18
+ } & MaterialOverrides & ShadowOverrides>): import("react").JSX.Element;
20
19
  export {};
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Surface } from "./surface.js";
3
3
  /** A native button uses Surface without changing its native contract. */
4
- export function SurfaceButton({ native, paint, material: options }) {
4
+ export function SurfaceButton({ native, paint, material: options, shadow }) {
5
5
  const { ref, style, children, ...properties } = native;
6
- return _jsx(Surface, { ...properties, as: "button", color: paint.background, material: options, ref: ref, style: { ...style, color: paint.color }, children: children });
6
+ return _jsx(Surface, { ...properties, as: "button", color: paint.background, material: options, shadow: shadow, ref: ref, style: { ...style, color: paint.color }, children: children });
7
7
  }
8
8
  /** Void text controls use a material host while retaining native sizing. */
9
- export function SurfaceField({ paint, radius, children, material: options }) {
10
- return _jsx(Surface, { as: "span", color: paint.background, material: options, radius: radius, style: { color: paint.color, display: "grid", minWidth: 0 }, children: children });
9
+ export function SurfaceField({ paint, radius, children, material: options, shadow }) {
10
+ return _jsx(Surface, { as: "span", color: paint.background, material: options, shadow: shadow, radius: radius, style: { color: paint.color, display: "grid", minWidth: 0 }, children: children });
11
11
  }
package/dist/control.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
3
  import { FieldError, Label, Text } from "react-aria-components";
4
- import { useAppearance, useThemedValue } from "./appearance-provider.js";
4
+ import { useAppearance, useThemedValue } from "./ui-provider.js";
5
5
  import { resolveRadius } from "./radius.js";
6
6
  import { scale } from "./scale.js";
7
7
  import { colorOpacity, solidColors } from "./color.js";
@@ -0,0 +1,46 @@
1
+ import type { ComponentProps, CSSProperties, HTMLAttributes, ReactNode } from "react";
2
+ import type { DialogProps as AriaDialogProps, DialogTriggerProps as AriaDialogTriggerProps, HeadingProps as AriaHeadingProps, ModalOverlayProps as AriaModalOverlayProps, TextProps as AriaTextProps } from "react-aria-components";
3
+ import { type ButtonProps } from "./button.js";
4
+ import { type SurfaceOwnProps } from "./surface.js";
5
+ export type DialogRootProps = AriaDialogTriggerProps;
6
+ export declare function DialogRoot(properties: DialogRootProps): import("react").JSX.Element;
7
+ export type DialogTriggerProps = ButtonProps;
8
+ export declare const DialogTrigger: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
9
+ export interface DialogBackdropProps extends Omit<AriaModalOverlayProps, "children" | "className" | "style"> {
10
+ readonly children?: ReactNode;
11
+ readonly className?: string;
12
+ readonly style?: CSSProperties;
13
+ }
14
+ export declare const DialogBackdrop: import("react").ForwardRefExoticComponent<DialogBackdropProps & import("react").RefAttributes<HTMLDivElement>>;
15
+ export interface DialogContentProps extends Omit<AriaDialogProps, "children" | "className" | "style">, SurfaceOwnProps {
16
+ readonly children?: AriaDialogProps["children"];
17
+ readonly className?: string;
18
+ readonly style?: CSSProperties;
19
+ }
20
+ export declare const DialogContent: import("react").ForwardRefExoticComponent<DialogContentProps & import("react").RefAttributes<HTMLElement>>;
21
+ export type DialogHeaderProps = HTMLAttributes<HTMLDivElement>;
22
+ export declare const DialogHeader: import("react").ForwardRefExoticComponent<DialogHeaderProps & import("react").RefAttributes<HTMLDivElement>>;
23
+ export type DialogTitleProps = AriaHeadingProps;
24
+ export declare const DialogTitle: import("react").ForwardRefExoticComponent<AriaHeadingProps & import("react").RefAttributes<HTMLHeadingElement>>;
25
+ export type DialogDescriptionProps = AriaTextProps;
26
+ export declare const DialogDescription: import("react").ForwardRefExoticComponent<AriaTextProps & import("react").RefAttributes<HTMLElement>>;
27
+ export type DialogBodyProps = HTMLAttributes<HTMLDivElement>;
28
+ export declare const DialogBody: import("react").ForwardRefExoticComponent<DialogBodyProps & import("react").RefAttributes<HTMLDivElement>>;
29
+ export type DialogFooterProps = HTMLAttributes<HTMLDivElement>;
30
+ export declare const DialogFooter: import("react").ForwardRefExoticComponent<DialogFooterProps & import("react").RefAttributes<HTMLDivElement>>;
31
+ export type DialogCloseProps = ButtonProps;
32
+ export declare const DialogClose: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
33
+ /** A modal dialog whose behavioral and structural roles remain independently composable. */
34
+ export declare const Dialog: typeof DialogRoot & {
35
+ Root: typeof DialogRoot;
36
+ Trigger: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
37
+ Backdrop: import("react").ForwardRefExoticComponent<DialogBackdropProps & import("react").RefAttributes<HTMLDivElement>>;
38
+ Content: import("react").ForwardRefExoticComponent<DialogContentProps & import("react").RefAttributes<HTMLElement>>;
39
+ Header: import("react").ForwardRefExoticComponent<DialogHeaderProps & import("react").RefAttributes<HTMLDivElement>>;
40
+ Title: import("react").ForwardRefExoticComponent<AriaHeadingProps & import("react").RefAttributes<HTMLHeadingElement>>;
41
+ Description: import("react").ForwardRefExoticComponent<AriaTextProps & import("react").RefAttributes<HTMLElement>>;
42
+ Body: import("react").ForwardRefExoticComponent<DialogBodyProps & import("react").RefAttributes<HTMLDivElement>>;
43
+ Footer: import("react").ForwardRefExoticComponent<DialogFooterProps & import("react").RefAttributes<HTMLDivElement>>;
44
+ Close: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
45
+ };
46
+ export type DialogProps = ComponentProps<typeof DialogRoot>;
package/dist/dialog.js ADDED
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
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
+ import { useAppearance } from "./ui-provider.js";
5
+ import { Button } from "./button.js";
6
+ import { backdropMotionClass, overlayMotionClass, useOverlayTransition } from "./motion-style.js";
7
+ import { scale } from "./scale.js";
8
+ import { Surface } from "./surface.js";
9
+ import { resolveDirection, useDirection } from "./direction.js";
10
+ export function DialogRoot(properties) {
11
+ return _jsx(AriaDialogTrigger, { ...properties });
12
+ }
13
+ export const DialogTrigger = forwardRef(function DialogTrigger(properties, ref) {
14
+ return _jsx(Button, { ...properties, ref: ref });
15
+ });
16
+ export const DialogBackdrop = forwardRef(function DialogBackdrop({ className, style, ...properties }, ref) {
17
+ const inset = scale(useAppearance().spacing, "medium");
18
+ const transition = useOverlayTransition();
19
+ const direction = resolveDirection(properties.dir, useDirection());
20
+ return _jsx(AriaModalOverlay, { ...properties, ref: ref, dir: direction, className: [backdropMotionClass, className].filter(Boolean).join(" "), style: {
21
+ ...transition,
22
+ position: "fixed",
23
+ inset: 0,
24
+ zIndex: 1_000,
25
+ display: "grid",
26
+ placeItems: "center",
27
+ boxSizing: "border-box",
28
+ padding: inset,
29
+ overflow: "auto",
30
+ background: "rgb(0 0 0 / 0.32)",
31
+ ...style
32
+ } });
33
+ });
34
+ export const DialogContent = forwardRef(function DialogContent({ children, className, color, material, radius, shadow, style, ...properties }, ref) {
35
+ const inset = scale(useAppearance().spacing, "medium");
36
+ const transition = useOverlayTransition();
37
+ const direction = resolveDirection(properties.dir, useDirection());
38
+ return _jsx(AriaModal, { dir: direction, className: overlayMotionClass, style: {
39
+ ...transition,
40
+ width: `min(32rem, calc(100vw - ${inset * 2}px))`,
41
+ maxHeight: `calc(100vh - ${inset * 2}px)`,
42
+ outline: "none"
43
+ }, children: _jsx(Surface, { className: className, color: color, material: material, radius: radius, shadow: shadow, style: {
44
+ boxSizing: "border-box",
45
+ width: "100%",
46
+ maxHeight: "inherit",
47
+ padding: inset,
48
+ overflow: "hidden",
49
+ ...style
50
+ }, children: _jsx(AriaDialog, { ...properties, ref: ref, style: {
51
+ display: "grid",
52
+ gap: inset,
53
+ minWidth: 0,
54
+ minHeight: 0,
55
+ maxHeight: "inherit",
56
+ outline: "none"
57
+ }, children: children }) }) });
58
+ });
59
+ export const DialogHeader = forwardRef(function DialogHeader({ style, ...properties }, ref) {
60
+ const gap = scale(useAppearance().spacing, "small");
61
+ return _jsx("div", { ...properties, ref: ref, style: { display: "grid", gap, minWidth: 0, ...style } });
62
+ });
63
+ export const DialogTitle = forwardRef(function DialogTitle({ style, ...properties }, ref) {
64
+ return _jsx(AriaHeading, { ...properties, ref: ref, slot: "title", style: { margin: 0, font: "inherit", fontWeight: 650, ...style } });
65
+ });
66
+ export const DialogDescription = forwardRef(function DialogDescription({ style, ...properties }, ref) {
67
+ return _jsx(AriaText, { ...properties, ref: ref, slot: "description", style: { margin: 0, opacity: 0.72, ...style } });
68
+ });
69
+ export const DialogBody = forwardRef(function DialogBody({ style, ...properties }, ref) {
70
+ return _jsx("div", { ...properties, ref: ref, style: { minWidth: 0, minHeight: 0, ...style } });
71
+ });
72
+ export const DialogFooter = forwardRef(function DialogFooter({ style, ...properties }, ref) {
73
+ const gap = scale(useAppearance().spacing, "small");
74
+ return _jsx("div", { ...properties, ref: ref, style: { display: "flex", justifyContent: "flex-end", flexWrap: "wrap", gap, minWidth: 0, ...style } });
75
+ });
76
+ export const DialogClose = forwardRef(function DialogClose(properties, ref) {
77
+ return _jsx(Button, { ...properties, ref: ref, slot: "close" });
78
+ });
79
+ /** A modal dialog whose behavioral and structural roles remain independently composable. */
80
+ export const Dialog = Object.assign(DialogRoot, {
81
+ Root: DialogRoot,
82
+ Trigger: DialogTrigger,
83
+ Backdrop: DialogBackdrop,
84
+ Content: DialogContent,
85
+ Header: DialogHeader,
86
+ Title: DialogTitle,
87
+ Description: DialogDescription,
88
+ Body: DialogBody,
89
+ Footer: DialogFooter,
90
+ Close: DialogClose
91
+ });
@@ -0,0 +1,9 @@
1
+ /** The two concrete directions supported by React UI behavior. */
2
+ export type Direction = "ltr" | "rtl";
3
+ export declare const DirectionContext: import("react").Context<Direction | null>;
4
+ /** Resolves a concrete element override before falling back to its React UI environment. */
5
+ export declare function resolveDirection(value: string | undefined, fallback: Direction): Direction;
6
+ /** Returns the nearest guaranteed React UI direction, or the document direction. */
7
+ export declare function useDirection(): Direction;
8
+ /** Reactively reads the explicit direction of the document's HTML element. */
9
+ export declare function useDocumentDirection(): Direction;
@@ -0,0 +1,31 @@
1
+ import { createContext, useContext, useSyncExternalStore } from "react";
2
+ export const DirectionContext = createContext(null);
3
+ /** Resolves a concrete element override before falling back to its React UI environment. */
4
+ export function resolveDirection(value, fallback) {
5
+ return value === "ltr" || value === "rtl" ? value : fallback;
6
+ }
7
+ /** Returns the nearest guaranteed React UI direction, or the document direction. */
8
+ export function useDirection() {
9
+ const direction = useContext(DirectionContext);
10
+ const documentDirection = useDocumentDirection();
11
+ return direction ?? documentDirection;
12
+ }
13
+ /** Reactively reads the explicit direction of the document's HTML element. */
14
+ export function useDocumentDirection() {
15
+ return useSyncExternalStore(subscribeDocumentDirection, documentDirection, serverDirection);
16
+ }
17
+ function subscribeDocumentDirection(change) {
18
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined")
19
+ return () => undefined;
20
+ const observer = new MutationObserver(change);
21
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["dir"] });
22
+ return () => observer.disconnect();
23
+ }
24
+ function documentDirection() {
25
+ if (typeof document === "undefined")
26
+ return serverDirection();
27
+ return document.documentElement.getAttribute("dir")?.toLowerCase() === "rtl" ? "rtl" : "ltr";
28
+ }
29
+ function serverDirection() {
30
+ return "ltr";
31
+ }
@@ -0,0 +1,21 @@
1
+ import type { ComponentProps } from "react";
2
+ import type { MenuTriggerProps as AriaMenuTriggerProps } from "react-aria-components";
3
+ import { type ButtonProps } from "./button.js";
4
+ export type DropdownMenuRootProps = AriaMenuTriggerProps;
5
+ export declare function DropdownMenuRoot(properties: DropdownMenuRootProps): import("react").JSX.Element;
6
+ export type DropdownMenuTriggerProps = ButtonProps;
7
+ export declare const DropdownMenuTrigger: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
8
+ /** A button-triggered overlay. Its Content owns positioning; Menu owns commands and selection. */
9
+ export declare const DropdownMenu: typeof DropdownMenuRoot & {
10
+ Root: typeof DropdownMenuRoot;
11
+ Trigger: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
12
+ Content: import("react").ForwardRefExoticComponent<import("./popover.js").PopoverContentProps & import("react").RefAttributes<HTMLElement>>;
13
+ Menu: import("./menu.js").MenuRootComponent & {
14
+ Root: import("./menu.js").MenuRootComponent;
15
+ Item: import("./menu.js").MenuItemComponent;
16
+ Section: <T>(props: import("react-aria-components").MenuSectionProps<T> & React.RefAttributes<HTMLElement>) => import("react").ReactElement<unknown, string | React.JSXElementConstructor<any>> | null;
17
+ Header: import("react").ForwardRefExoticComponent<import("react-aria-components").HeaderProps & import("react").RefAttributes<HTMLElement>>;
18
+ Separator: import("react").ForwardRefExoticComponent<import("react-aria-components").SeparatorProps & import("react").RefAttributes<HTMLElement>>;
19
+ };
20
+ };
21
+ export type DropdownMenuProps = ComponentProps<typeof DropdownMenuRoot>;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { MenuTrigger as AriaMenuTrigger } from "react-aria-components";
4
+ import { Button } from "./button.js";
5
+ import { Menu } from "./menu.js";
6
+ import { PopoverContent } from "./popover.js";
7
+ export function DropdownMenuRoot(properties) {
8
+ return _jsx(AriaMenuTrigger, { ...properties });
9
+ }
10
+ export const DropdownMenuTrigger = forwardRef(function DropdownMenuTrigger(properties, ref) {
11
+ return _jsx(Button, { ...properties, ref: ref });
12
+ });
13
+ /** A button-triggered overlay. Its Content owns positioning; Menu owns commands and selection. */
14
+ export const DropdownMenu = Object.assign(DropdownMenuRoot, {
15
+ Root: DropdownMenuRoot,
16
+ Trigger: DropdownMenuTrigger,
17
+ Content: PopoverContent,
18
+ Menu
19
+ });
package/dist/flex.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { alignment, justification, resolveGap } from "./layout.js";
4
- import { useAppearance } from "./appearance-provider.js";
4
+ import { useAppearance } from "./ui-provider.js";
5
5
  /** A predictable Flexbox container with no visual appearance of its own. */
6
6
  export const Flex = forwardRef(function Flex({ align, direction, gap, inline = false, justify, style, wrap, ...properties }, ref) {
7
7
  const appearance = useAppearance();
package/dist/grid.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { alignment, justification, resolveGap, tracks } from "./layout.js";
4
- import { useAppearance } from "./appearance-provider.js";
4
+ import { useAppearance } from "./ui-provider.js";
5
5
  /** A predictable CSS Grid container with no visual appearance of its own. */
6
6
  export const Grid = forwardRef(function Grid({ align, columns, flow, gap, inline = false, justify, rows, style, ...properties }, ref) {
7
7
  const appearance = useAppearance();