@phreshos/react-ui 0.1.24 → 0.1.26

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 +74 -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 +18 -0
  63. package/package.json +2 -2
  64. package/dist/appearance-provider.d.ts +0 -10
  65. package/dist/appearance-provider.js +0 -13
package/README.md CHANGED
@@ -38,22 +38,38 @@ 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 shared by a React UI subtree. Omitted
43
+ Appearance and Preferences values inherit from the nearest provider.
43
44
  React UI also exports that same canonical `defaultAppearance` value for callers
44
45
  that need it explicitly.
45
46
  See [Appearance](https://docs.phreshos.com/system/appearance) for the contract
46
47
  interpreted by the provider and components.
47
48
 
49
+ React UI is neutral between left-to-right and right-to-left interfaces.
50
+ `UIProvider` creates a direction boundary that participates in native DOM
51
+ inheritance without creating a layout box. Its direction follows `<html dir>`
52
+ unless `direction` supplies an explicit override. React UI reads that same
53
+ resolved value only for JavaScript behavior and portals that cannot rely on
54
+ native inheritance.
55
+
56
+ ```tsx
57
+ import { UIProvider, Select } from "@phreshos/react-ui"
58
+
59
+ <UIProvider direction="rtl">
60
+ <Select label="Choice" options={[{ value: "one", label: "One" }]} />
61
+ </UIProvider>
62
+ ```
63
+
48
64
  ```tsx
49
- import { AppearanceProvider, Button, useBrowserPreferences } from "@phreshos/react-ui"
65
+ import { UIProvider, Button, useBrowserPreferences } from "@phreshos/react-ui"
50
66
 
51
67
  function Example() {
52
68
  const browser = useBrowserPreferences()
53
69
 
54
- return <AppearanceProvider preferences={{ ...browser, theme: "dark" }}>
70
+ return <UIProvider preferences={{ ...browser, theme: "dark" }}>
55
71
  <Button>Dark subtree</Button>
56
- </AppearanceProvider>
72
+ </UIProvider>
57
73
  }
58
74
  ```
59
75
 
@@ -75,12 +91,11 @@ spacing and radius follow Appearance. `disabled` prevents activation and focus;
75
91
  <Button color="danger:soft" size="small">Delete</Button>
76
92
  ```
77
93
 
78
- `Surface` is the material-owning element. It renders a `div` by default, while
94
+ `Surface` is the material- and shadow-owning element. It renders a `div` by default, while
79
95
  `as` selects another React element and preserves that element's native properties
80
96
  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`.
97
+ outer shadow, radius, and clipping requirements. `radius` accepts a size level, `full`, a
98
+ number in pixels, or a CSS radius and defaults to `medium`.
84
99
 
85
100
  ```tsx
86
101
  <Surface color="background:soft" radius="large">Derived values</Surface>
@@ -89,27 +104,35 @@ pixels, or a CSS radius and defaults to `medium`.
89
104
  ```
90
105
 
91
106
  `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.
107
+ the `style`, `dir`, and `children` it receives on one host element and forwards
108
+ its ref to that same element. This lets layout components carry the material
109
+ without a wrapper. The host retains ownership of its own behavior and layout
110
+ properties; Surface retains ownership of material, edge, shadow, radius, and
111
+ required geometry.
96
112
 
97
113
  `MaterialOptions` defines `opacity`, `backdrop`, `grain`, `grainAmount`,
98
114
  `distortion`, and `saturation`. Every material-bearing component, including
99
115
  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;
116
+ separate property. Omission or `true` uses `appearance.material`; `false`
117
+ removes the material and paints the resolved color as a normal background.
118
+ Effect options accept a scale level or a direct number;
102
119
  opacity affects Surface paint only, never its content. The material edge is part
103
120
  of the same Surface rather than a second public entity.
104
121
 
105
122
  Surface-based controls expose the same separate `color` and `material` props.
123
+ `ShadowOptions` similarly groups `x`, `y`, `blur`, `spread`, and `opacity` under
124
+ the `shadow` prop. Omission or `true` follows the active `appearance.shadow`
125
+ branch, while `false` removes the shadow. Each option accepts a scale level or
126
+ direct number. The shadow is a neutral black
127
+ outer shadow. Surface-based controls expose the same `shadow` prop.
106
128
  For text fields and Select, `material` targets the field or trigger; for Checkbox,
107
129
  Switch, and Radio, it targets the indicator. RadioGroup supplies material defaults
108
- to its options, and a Radio can override them.
130
+ to its options, and a Radio can override them. Shadow follows the same targets and
131
+ inheritance path.
109
132
 
110
133
  ```tsx
111
134
  <Button color="primary:base" material={{ opacity: 0.6, backdrop: 0 }}>Save</Button>
112
- <Input label="Name" radius="large" material={{ grain: "small" }} />
135
+ <Input label="Name" radius="large" material={{ grain: "small" }} shadow={{ blur: "small" }} />
113
136
  ```
114
137
 
115
138
  `Panel` composes an outer `Surface`, an optional header, and an inset content
@@ -119,13 +142,41 @@ Appearance spacing. Positioning, modality, and lifecycle belong to the caller.
119
142
  ```tsx
120
143
  import { Panel } from "@phreshos/react-ui"
121
144
 
122
- <Panel header={<h2>Title</h2>} contentProps={{ style: { padding: 16 } }}>
123
- Content
145
+ <Panel>
146
+ <Panel.Header><h2>Title</h2></Panel.Header>
147
+ <Panel.Content style={{ padding: 16 }}>Content</Panel.Content>
124
148
  </Panel>
125
149
  ```
126
150
 
127
- Native properties and the forwarded ref target the outer Surface.
128
- `contentProps` targets the inner Surface; `children` supplies its content.
151
+ Native properties and the root ref target the outer Surface. `Panel.Header`
152
+ owns the optional leading region, while `Panel.Content` is the independently
153
+ configurable inner Surface.
154
+
155
+ ## Overlays
156
+
157
+ Overlay components expose each behavioral role as a named part. Roots own open
158
+ state, triggers own activation, positioned content owns its Surface, and the
159
+ semantic content remains explicit.
160
+
161
+ ```tsx
162
+ <DropdownMenu>
163
+ <DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
164
+ <DropdownMenu.Content>
165
+ <Menu aria-label="Document actions">
166
+ <Menu.Item onAction={open}>Open</Menu.Item>
167
+ <Menu.Item color="danger:base" onAction={remove}>Delete</Menu.Item>
168
+ </Menu>
169
+ </DropdownMenu.Content>
170
+ </DropdownMenu>
171
+ ```
172
+
173
+ `Popover` presents anchored non-modal content. `DropdownMenu` and
174
+ `ContextMenu` open the same `Menu` contract through different interactions.
175
+ `Dialog` blocks interaction behind it and may be dismissable, while
176
+ `AlertDialog` requires an explicit decision by default. `Tooltip` supplies a
177
+ short description on focus or hover. Portal cleanup, focus restoration,
178
+ keyboard behavior, nested ownership, and entrance and exit transactions remain
179
+ component-owned.
129
180
 
130
181
  ## Inputs
131
182
 
@@ -143,8 +194,8 @@ Server SDK.
143
194
  Fields distinguish hover, pointer focus, keyboard focus, and invalid state.
144
195
  Shared CSS transitions use `appearance.transaction` for colors and corner
145
196
  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
197
+ Surface host. Select menus scale from `1.05` to `1` while fading in, and reverse
198
+ that motion when exiting, using React Aria's animation lifecycle. Preferences with animations disabled make
148
199
  these changes immediate without changing the Appearance value.
149
200
  Blur, distortion, geometry, and Surface host opacity are not transitioned; gradients
150
201
  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();