@phreshos/react-ui 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -31
- package/dist/button.d.ts +1 -1
- package/dist/button.js +2 -2
- package/dist/checkbox.d.ts +1 -1
- package/dist/{control-material.d.ts → control-surface.d.ts} +3 -3
- package/dist/control-surface.js +11 -0
- package/dist/input.js +2 -2
- package/dist/main.d.ts +1 -2
- package/dist/main.js +0 -1
- package/dist/material-options.d.ts +5 -1
- package/dist/material-options.js +1 -1
- package/dist/material-paint.d.ts +1 -1
- package/dist/material-paint.js +1 -1
- package/dist/radio.d.ts +1 -1
- package/dist/select.d.ts +1 -1
- package/dist/select.js +2 -2
- package/dist/surface-edge.d.ts +5 -2
- package/dist/surface.d.ts +21 -5
- package/dist/surface.js +51 -10
- package/dist/switch.d.ts +1 -1
- package/dist/text-control.d.ts +1 -1
- package/dist/textarea.js +2 -2
- package/dist/toggle-indicator.js +8 -11
- package/package.json +1 -1
- package/dist/control-material.js +0 -14
- package/dist/material.d.ts +0 -27
- package/dist/material.js +0 -45
package/README.md
CHANGED
|
@@ -54,9 +54,9 @@ import { AppearanceProvider, Button } from "@phreshos/react-ui"
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
`Button`, `Input`, `Textarea`, `Select`, `Checkbox`, `Switch`, and `Radio`
|
|
57
|
-
|
|
57
|
+
use the shared Surface implementation while retaining their native behavior. Color and material
|
|
58
58
|
are independent inputs. `color` accepts an Appearance color and resting level such
|
|
59
|
-
as `background:base` or `primary:soft`, or a direct CSS color.
|
|
59
|
+
as `background:base` or `primary:soft`, or a direct CSS color. Surface and neutral
|
|
60
60
|
surface hosts default to `background:base`; a component may select a semantic
|
|
61
61
|
default required by its own behavior, such as `primary:base` for selection.
|
|
62
62
|
Hover and press derive shades of that fill. The resting fill chooses the
|
|
@@ -72,40 +72,34 @@ spacing and radius follow Appearance. `disabled` prevents activation and focus;
|
|
|
72
72
|
<Button color="danger:soft" size="small">Delete</Button>
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<div style={{ position: "relative", isolation: "isolate", width: 240, height: 120, borderRadius: 18 }}>
|
|
82
|
-
<Material color="background:soft" material={{ opacity: 0.4 }} />
|
|
83
|
-
</div>
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
`Surface` is the standard geometric host for a `Material`. It establishes the
|
|
87
|
-
required positioning and isolation on a `div`, resolves radius, renders Material,
|
|
88
|
-
and then renders its content. It never creates or consumes a shadow. Shadow remains
|
|
89
|
-
an independent visual concern. `radius` accepts a size level, a number in pixels,
|
|
90
|
-
or a CSS radius and defaults to `medium`.
|
|
75
|
+
`Surface` is the material-owning element. It renders a `div` by default, while
|
|
76
|
+
`as` selects another React element and preserves that element's native properties
|
|
77
|
+
and ref type. Surface owns its paint, opacity, frost, refraction, grain, edge,
|
|
78
|
+
radius, and clipping requirements. It never creates or consumes a shadow; shadow
|
|
79
|
+
remains an independent visual concern. `radius` accepts a size level, a number in
|
|
80
|
+
pixels, or a CSS radius and defaults to `medium`.
|
|
91
81
|
|
|
92
82
|
```tsx
|
|
93
83
|
<Surface color="background:soft" radius="large">Derived values</Surface>
|
|
94
|
-
<Surface color="#345678" radius={18}>
|
|
84
|
+
<Surface as="button" type="button" color="#345678" radius={18}>Action</Surface>
|
|
85
|
+
<Surface as={Grid} columns={3} gap="medium">Grid content</Surface>
|
|
95
86
|
```
|
|
96
87
|
|
|
97
|
-
`
|
|
98
|
-
`
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
only, never the host's children.
|
|
103
|
-
|
|
104
|
-
Radius belongs to host geometry, while Surface's border belongs to its geometric
|
|
105
|
-
boundary. Material may determine the border treatment, but it neither owns nor
|
|
106
|
-
renders that border.
|
|
88
|
+
`as` can also select an outside React component. A valid Surface host preserves
|
|
89
|
+
the `style` and `children` it receives on one host element and forwards its ref
|
|
90
|
+
to that same element. This lets layout components carry the material without a
|
|
91
|
+
wrapper. The host retains ownership of its own behavior and layout properties;
|
|
92
|
+
Surface retains ownership of material, edge, radius, and required geometry.
|
|
107
93
|
|
|
108
|
-
|
|
94
|
+
`MaterialOptions` defines `opacity`, `backdrop`, `grain`, `grainAmount`,
|
|
95
|
+
`distortion`, and `saturation`. Surface accepts these properties directly because
|
|
96
|
+
it is the material-owning element; it does not accept a nested `material` prop.
|
|
97
|
+
Color remains a separate Surface property. Omitted material values follow
|
|
98
|
+
`appearance.material`. Effect options accept a scale level or a direct number;
|
|
99
|
+
opacity affects Surface paint only, never its content. The material edge is part
|
|
100
|
+
of the same Surface rather than a second public entity.
|
|
101
|
+
|
|
102
|
+
Surface-based controls expose the same separate `color` and `material` props.
|
|
109
103
|
For text fields and Select, `material` targets the field or trigger; for Checkbox,
|
|
110
104
|
Switch, and Radio, it targets the indicator. RadioGroup supplies material defaults
|
|
111
105
|
to its options, and a Radio can override them.
|
|
@@ -146,7 +140,7 @@ Server SDK.
|
|
|
146
140
|
|
|
147
141
|
Fields distinguish hover, pointer focus, keyboard focus, and invalid state.
|
|
148
142
|
Shared CSS transitions use a fixed 120ms ease-out timing for colors and corner
|
|
149
|
-
radius.
|
|
143
|
+
radius. The material fill and opacity values transition on the painted layers, not on the
|
|
150
144
|
Surface host. Select menus combine a small placement-aware slide with an overlay
|
|
151
145
|
opacity fade for entry and exit, using React Aria's animation lifecycle. Reduced-motion preferences make
|
|
152
146
|
these changes immediate. No Appearance configuration is added for motion yet.
|
package/dist/button.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ButtonProps as AriaButtonProps } from "react-aria-components";
|
|
|
3
3
|
import type { ScaleLevel } from "./scale.js";
|
|
4
4
|
import type { RadiusProps } from "./radius.js";
|
|
5
5
|
import { type ControlColor } from "./control.js";
|
|
6
|
-
import type { MaterialOverrides } from "./material.js";
|
|
6
|
+
import type { MaterialOverrides } from "./material-options.js";
|
|
7
7
|
type NativeButtonProps = Omit<AriaButtonProps, "children" | "className" | "color" | "isDisabled" | "isPending" | "onClick" | "onPress" | "style">;
|
|
8
8
|
/** A semantic color from Appearance; omission uses background and foreground. */
|
|
9
9
|
export type ButtonColor = ControlColor;
|
package/dist/button.js
CHANGED
|
@@ -2,12 +2,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import { Button as AriaButton } from "react-aria-components";
|
|
4
4
|
import { controlFontSizes, useControlTheme } from "./control.js";
|
|
5
|
-
import {
|
|
5
|
+
import { SurfaceButton } from "./control-surface.js";
|
|
6
6
|
import { visualTransition } from "./motion-style.js";
|
|
7
7
|
/** An Appearance-aware action with normalized pointer and keyboard behavior. */
|
|
8
8
|
export const Button = forwardRef(function Button({ children, color, disabled = false, pending = false, onPress, radius = "medium", size = "medium", style, material, type = "button", ...properties }, ref) {
|
|
9
9
|
const theme = useControlTheme({ color, radius, size });
|
|
10
|
-
return _jsx(AriaButton, { ...properties, ref: ref, type: type, isDisabled: disabled, isPending: pending, onPress: onPress, render: (native, state) => _jsx(
|
|
10
|
+
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({
|
|
11
11
|
theme,
|
|
12
12
|
disabled,
|
|
13
13
|
isFocusVisible,
|
package/dist/checkbox.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CheckboxFieldProps } from "react-aria-components";
|
|
2
2
|
import type { ControlOverrides, ControlProps, FieldProps } from "./control.js";
|
|
3
|
-
import type { MaterialOverrides } from "./material.js";
|
|
3
|
+
import type { MaterialOverrides } from "./material-options.js";
|
|
4
4
|
export interface CheckboxProps extends Omit<CheckboxFieldProps, ControlOverrides | "isReadOnly" | "isSelected" | "defaultSelected" | "isIndeterminate">, ControlProps, FieldProps, MaterialOverrides {
|
|
5
5
|
readonly checked?: boolean;
|
|
6
6
|
readonly defaultChecked?: boolean;
|
|
@@ -4,14 +4,14 @@ type Paint = Readonly<{
|
|
|
4
4
|
background: string;
|
|
5
5
|
color: string;
|
|
6
6
|
}>;
|
|
7
|
-
/** A native button
|
|
8
|
-
export declare function
|
|
7
|
+
/** A native button uses Surface without changing its native contract. */
|
|
8
|
+
export declare function SurfaceButton({ native, paint, material: options }: {
|
|
9
9
|
readonly native: ComponentPropsWithRef<"button">;
|
|
10
10
|
readonly paint: Paint;
|
|
11
11
|
readonly material?: MaterialOptions;
|
|
12
12
|
}): import("react").JSX.Element;
|
|
13
13
|
/** Void text controls use a material host while retaining native sizing. */
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function SurfaceField({ paint, radius, children, material: options }: Readonly<{
|
|
15
15
|
paint: Paint;
|
|
16
16
|
radius: CSSProperties["borderRadius"];
|
|
17
17
|
children: ReactNode;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Surface } from "./surface.js";
|
|
3
|
+
/** A native button uses Surface without changing its native contract. */
|
|
4
|
+
export function SurfaceButton({ native, paint, material: options }) {
|
|
5
|
+
const { ref, style, children, ...properties } = native;
|
|
6
|
+
return _jsx(Surface, { ...properties, ...options, as: "button", color: paint.background, ref: ref, style: { ...style, color: paint.color }, children: children });
|
|
7
|
+
}
|
|
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, { ...options, as: "span", color: paint.background, radius: radius, style: { color: paint.color, display: "grid", minWidth: 0 }, children: children });
|
|
11
|
+
}
|
package/dist/input.js
CHANGED
|
@@ -2,10 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import { Input as AriaInput, TextField } from "react-aria-components";
|
|
4
4
|
import { controlPaint, controlStyle, FieldFeedback, FieldLabel, fieldStyle, useControlTheme } from "./control.js";
|
|
5
|
-
import {
|
|
5
|
+
import { SurfaceField } from "./control-surface.js";
|
|
6
6
|
import FieldStyle, { textControlClass } from "./field-style.js";
|
|
7
7
|
/** A labeled single-line text control. onChange receives the string value. */
|
|
8
8
|
export const Input = forwardRef(function Input({ label, description, errorMessage, disabled, readOnly, required, invalid, size, color, radius, style, className, placeholder, material, ...properties }, ref) {
|
|
9
9
|
const theme = useControlTheme({ size, color, radius });
|
|
10
|
-
return _jsxs(TextField, { ...properties, className: className, isDisabled: disabled, isReadOnly: readOnly, isRequired: required, isInvalid: invalid, style: fieldStyle(theme, disabled, style), children: [_jsx(FieldStyle, {}), _jsx(FieldLabel, { label: label }), _jsx(AriaInput, { ref: ref, placeholder: placeholder, className: textControlClass, render: (native, state) => _jsx(
|
|
10
|
+
return _jsxs(TextField, { ...properties, className: className, isDisabled: disabled, isReadOnly: readOnly, isRequired: required, isInvalid: invalid, style: fieldStyle(theme, disabled, style), children: [_jsx(FieldStyle, {}), _jsx(FieldLabel, { label: label }), _jsx(AriaInput, { ref: ref, placeholder: placeholder, className: textControlClass, render: (native, state) => _jsx(SurfaceField, { material: material, radius: theme.radius, paint: controlPaint(theme, state.isFocused, state.isInvalid, state.isHovered), children: _jsx("input", { ...native, style: { ...native.style, borderRadius: "inherit", background: "transparent" } }) }), style: state => controlStyle(theme, state.isFocused, state.isInvalid, state.isHovered, state.isFocusVisible) }), _jsx(FieldFeedback, { theme: theme, description: description, errorMessage: errorMessage })] });
|
|
11
11
|
});
|
package/dist/main.d.ts
CHANGED
|
@@ -8,8 +8,7 @@ export { defaultAppearance } from "@phreshos/core";
|
|
|
8
8
|
export { AppearanceProvider, useAppearance, useResolveTheme, useTheme, type AppearanceProviderProps } from "./appearance-provider.js";
|
|
9
9
|
export { Flex, type FlexProps } from "./flex.js";
|
|
10
10
|
export { Grid, type GridProps } from "./grid.js";
|
|
11
|
-
export {
|
|
12
|
-
export { Surface, type SurfaceProps } from "./surface.js";
|
|
11
|
+
export { Surface, type MaterialOptions, type SurfaceComponent, type SurfaceHost, type SurfaceHostProps, type SurfaceOwnProps, type SurfaceProps } from "./surface.js";
|
|
13
12
|
export { defaultColor, type Color, type ColorLevel, type ColorScale } from "./color.js";
|
|
14
13
|
export { Panel, type PanelProps } from "./panel.js";
|
|
15
14
|
export { Button, type ButtonColor, type ButtonProps } from "./button.js";
|
package/dist/main.js
CHANGED
|
@@ -8,7 +8,6 @@ export { defaultAppearance } from "@phreshos/core";
|
|
|
8
8
|
export { AppearanceProvider, useAppearance, useResolveTheme, useTheme } from "./appearance-provider.js";
|
|
9
9
|
export { Flex } from "./flex.js";
|
|
10
10
|
export { Grid } from "./grid.js";
|
|
11
|
-
export { Material } from "./material.js";
|
|
12
11
|
export { Surface } from "./surface.js";
|
|
13
12
|
export { defaultColor } from "./color.js";
|
|
14
13
|
export { Panel } from "./panel.js";
|
|
@@ -8,7 +8,11 @@ export interface MaterialOptions {
|
|
|
8
8
|
readonly distortion?: ScaleLevel | number;
|
|
9
9
|
readonly saturation?: ScaleLevel | number;
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
11
|
+
/** Grouped material customization exposed by components built on Surface. */
|
|
12
|
+
export interface MaterialOverrides {
|
|
13
|
+
readonly material?: MaterialOptions;
|
|
14
|
+
}
|
|
15
|
+
/** Resolves material values from the current Appearance without knowing any host geometry. */
|
|
12
16
|
export declare function useMaterialOptions(values?: MaterialOptions): {
|
|
13
17
|
opacity: number;
|
|
14
18
|
backdrop: number;
|
package/dist/material-options.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appearanceLimits } from "@phreshos/core";
|
|
2
2
|
import { useAppearance, useResolveTheme } from "./appearance-provider.js";
|
|
3
3
|
import { isScaleLevel, scale, scaleMultiplier } from "./scale.js";
|
|
4
|
-
/** Resolves
|
|
4
|
+
/** Resolves material values from the current Appearance without knowing any host geometry. */
|
|
5
5
|
export function useMaterialOptions(values = {}) {
|
|
6
6
|
const appearance = useAppearance();
|
|
7
7
|
const defaults = useResolveTheme(appearance.material);
|
package/dist/material-paint.d.ts
CHANGED
|
@@ -6,6 +6,6 @@ interface MaterialPaintProps {
|
|
|
6
6
|
readonly identity: string;
|
|
7
7
|
readonly opacity: number;
|
|
8
8
|
}
|
|
9
|
-
/** The fill, refraction definition, and grain of one
|
|
9
|
+
/** The fill, refraction definition, and grain of one Surface. */
|
|
10
10
|
export declare function MaterialPaint({ color, distortion, grain, grainAmount, identity, opacity }: MaterialPaintProps): import("react").JSX.Element | null;
|
|
11
11
|
export {};
|
package/dist/material-paint.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import { paintTransition } from "./motion-style.js";
|
|
4
|
-
/** The fill, refraction definition, and grain of one
|
|
4
|
+
/** The fill, refraction definition, and grain of one Surface. */
|
|
5
5
|
export function MaterialPaint({ color, distortion, grain, grainAmount, identity, opacity }) {
|
|
6
6
|
const seed = useMemo(() => seedFrom(identity), [identity]);
|
|
7
7
|
const hasPaint = opacity > 0;
|
package/dist/radio.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { RadioGroupProps as AriaRadioGroupProps, RadioFieldProps } from "react-aria-components";
|
|
3
3
|
import type { ControlOverrides, ControlProps, FieldProps } from "./control.js";
|
|
4
|
-
import type { MaterialOverrides } from "./material.js";
|
|
4
|
+
import type { MaterialOverrides } from "./material-options.js";
|
|
5
5
|
export interface RadioGroupProps extends Omit<AriaRadioGroupProps, ControlOverrides | "isReadOnly">, ControlProps, FieldProps, MaterialOverrides {
|
|
6
6
|
readonly children: ReactNode;
|
|
7
7
|
readonly readOnly?: boolean;
|
package/dist/select.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SelectProps as AriaSelectProps } from "react-aria-components";
|
|
2
2
|
import type { ControlOverrides, ControlProps, FieldProps } from "./control.js";
|
|
3
3
|
import type { RadiusProps } from "./radius.js";
|
|
4
|
-
import type { MaterialOverrides } from "./material.js";
|
|
4
|
+
import type { MaterialOverrides } from "./material-options.js";
|
|
5
5
|
export interface SelectOption {
|
|
6
6
|
readonly value: string;
|
|
7
7
|
readonly label: string;
|
package/dist/select.js
CHANGED
|
@@ -3,14 +3,14 @@ import { forwardRef } from "react";
|
|
|
3
3
|
import { motion, useReducedMotion } from "motion/react";
|
|
4
4
|
import { Select as AriaSelect, Button, SelectValue, Popover, ListBox, ListBoxItem } from "react-aria-components";
|
|
5
5
|
import { controlPaint, controlStyle, FieldFeedback, FieldLabel, fieldStyle, useControlTheme } from "./control.js";
|
|
6
|
-
import {
|
|
6
|
+
import { SurfaceButton } from "./control-surface.js";
|
|
7
7
|
import { Surface } from "./surface.js";
|
|
8
8
|
import { controlTransition, overlayMotionClass, visualTransition } from "./motion-style.js";
|
|
9
9
|
/** Single selection from string-valued options; keyboard navigation and typeahead stay native to React Aria. */
|
|
10
10
|
export const Select = forwardRef(function Select({ label, description, errorMessage, disabled, required, invalid, options, value, defaultValue, onChange, size, color, radius, style, material, ...properties }, ref) {
|
|
11
11
|
const theme = useControlTheme({ size, color, radius });
|
|
12
12
|
const reduced = useReducedMotion();
|
|
13
|
-
return _jsx(AriaSelect, { ...properties, ref: ref, value: value, defaultValue: defaultValue, onChange: key => onChange?.(key == null ? null : String(key)), disabledKeys: options.filter(option => option.disabled).map(option => option.value), isDisabled: disabled, isRequired: required, isInvalid: invalid, style: fieldStyle(theme, disabled, style), children: state => _jsxs(_Fragment, { children: [_jsx(FieldLabel, { label: label }), _jsxs(Button, { render: (native, button) => _jsx(
|
|
13
|
+
return _jsx(AriaSelect, { ...properties, ref: ref, value: value, defaultValue: defaultValue, onChange: key => onChange?.(key == null ? null : String(key)), disabledKeys: options.filter(option => option.disabled).map(option => option.value), isDisabled: disabled, isRequired: required, isInvalid: invalid, style: fieldStyle(theme, disabled, style), children: state => _jsxs(_Fragment, { children: [_jsx(FieldLabel, { label: label }), _jsxs(Button, { render: (native, button) => _jsx(SurfaceButton, { native: native, material: material, paint: controlPaint(theme, state.isOpen || button.isFocused, state.isInvalid, button.isHovered) }), style: button => ({
|
|
14
14
|
...controlStyle(theme, state.isOpen || button.isFocused, state.isInvalid, button.isHovered, button.isFocusVisible),
|
|
15
15
|
display: "flex", alignItems: "center", justifyContent: "space-between", gap: theme.gap,
|
|
16
16
|
cursor: disabled ? "not-allowed" : "pointer", textAlign: "start"
|
package/dist/surface-edge.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type EdgeMaterial = Readonly<{
|
|
2
|
+
color: string;
|
|
3
|
+
foreground: string;
|
|
4
|
+
opacity: number;
|
|
5
|
+
}>;
|
|
3
6
|
/** Paints the boundary owned by one Surface from the material it contains. */
|
|
4
7
|
export declare function SurfaceEdge({ material }: Readonly<{
|
|
5
8
|
material: EdgeMaterial;
|
package/dist/surface.d.ts
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import type { ComponentPropsWithRef } from "react";
|
|
2
|
-
import { type
|
|
1
|
+
import type { ComponentPropsWithRef, CSSProperties, ElementType, ReactElement, ReactNode } from "react";
|
|
2
|
+
import { type Color } from "./color.js";
|
|
3
|
+
import { type MaterialOptions } from "./material-options.js";
|
|
3
4
|
import { type RadiusProps } from "./radius.js";
|
|
4
|
-
|
|
5
|
-
export interface
|
|
5
|
+
export type { MaterialOptions } from "./material-options.js";
|
|
6
|
+
export interface SurfaceOwnProps extends MaterialOptions, RadiusProps {
|
|
7
|
+
readonly color?: Color;
|
|
6
8
|
}
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The structural properties a component must preserve when it hosts a Surface.
|
|
11
|
+
* The component must apply `style` and render `children` on the same host element,
|
|
12
|
+
* and it must forward its ref to that element.
|
|
13
|
+
*/
|
|
14
|
+
export interface SurfaceHostProps {
|
|
15
|
+
readonly children?: ReactNode;
|
|
16
|
+
readonly style?: CSSProperties;
|
|
17
|
+
}
|
|
18
|
+
export type SurfaceHost = ElementType<SurfaceHostProps>;
|
|
19
|
+
export type SurfaceProps<As extends SurfaceHost = "div"> = SurfaceOwnProps & Readonly<{
|
|
20
|
+
as?: As;
|
|
21
|
+
}> & Omit<ComponentPropsWithRef<As>, keyof SurfaceOwnProps | "as" | "color">;
|
|
22
|
+
export type SurfaceComponent = <As extends SurfaceHost = "div">(properties: SurfaceProps<As>) => ReactElement | null;
|
|
23
|
+
export declare const Surface: SurfaceComponent;
|
package/dist/surface.js
CHANGED
|
@@ -1,21 +1,62 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
|
-
import { useAppearance } from "./appearance-provider.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { createElement, forwardRef, useId } from "react";
|
|
3
|
+
import { useAppearance, useResolveTheme } from "./appearance-provider.js";
|
|
4
|
+
import { useResolveColor } from "./color.js";
|
|
5
|
+
import { useMaterialOptions } from "./material-options.js";
|
|
6
|
+
import { MaterialPaint } from "./material-paint.js";
|
|
7
|
+
import MotionStyle, { visualTransition } from "./motion-style.js";
|
|
6
8
|
import { resolveRadius } from "./radius.js";
|
|
7
9
|
import { SurfaceEdge } from "./surface-edge.js";
|
|
8
|
-
|
|
10
|
+
/** One material-owning element. The host is a div unless `as` selects another element. */
|
|
11
|
+
const SurfaceRoot = forwardRef(function Surface({ as: Element = "div", color, opacity, backdrop, grain, grainAmount, distortion, saturation, radius, children, style, ...properties }, ref) {
|
|
9
12
|
const appearance = useAppearance();
|
|
10
|
-
const material =
|
|
13
|
+
const material = useResolvedSurface(color, { opacity, backdrop, grain, grainAmount, distortion, saturation });
|
|
11
14
|
const resolvedRadius = resolveRadius(radius ?? "medium", appearance);
|
|
12
|
-
return
|
|
15
|
+
return createElement(Element, {
|
|
16
|
+
...properties,
|
|
17
|
+
ref,
|
|
18
|
+
style: {
|
|
13
19
|
...visualTransition,
|
|
14
|
-
background: "transparent",
|
|
15
|
-
color: material.foreground,
|
|
16
20
|
...style,
|
|
21
|
+
background: "transparent",
|
|
22
|
+
color: style?.color ?? material.foreground,
|
|
17
23
|
borderRadius: radius === undefined ? style?.borderRadius ?? resolvedRadius : resolvedRadius,
|
|
18
24
|
position: style?.position ?? "relative",
|
|
19
25
|
isolation: "isolate"
|
|
20
|
-
}
|
|
26
|
+
}
|
|
27
|
+
}, _jsx(SurfaceLayers, { material: material }), _jsx(SurfaceEdge, { material: material }), children);
|
|
21
28
|
});
|
|
29
|
+
export const Surface = SurfaceRoot;
|
|
30
|
+
function useResolvedSurface(color, options) {
|
|
31
|
+
const appearance = useAppearance();
|
|
32
|
+
const material = useMaterialOptions(options);
|
|
33
|
+
return {
|
|
34
|
+
...material,
|
|
35
|
+
color: useResolveColor(color),
|
|
36
|
+
foreground: useResolveTheme(appearance.colors.foreground)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const layerStyle = {
|
|
40
|
+
position: "absolute",
|
|
41
|
+
inset: 0,
|
|
42
|
+
borderRadius: "inherit",
|
|
43
|
+
pointerEvents: "none"
|
|
44
|
+
};
|
|
45
|
+
function SurfaceLayers({ material }) {
|
|
46
|
+
const { backdrop, saturation, distortion } = material;
|
|
47
|
+
const frost = [
|
|
48
|
+
backdrop === 0 ? "" : `blur(${backdrop}px)`,
|
|
49
|
+
saturation === 1 ? "" : `saturate(${saturation})`
|
|
50
|
+
].filter(Boolean).join(" ");
|
|
51
|
+
const identity = `phresh-material-${useId().replaceAll(":", "")}`;
|
|
52
|
+
return _jsxs("span", { "data-material": "", "aria-hidden": "true", style: layerStyle, children: [_jsx(MotionStyle, {}), distortion > 0 && _jsx(BackdropLayer, { name: "refraction", filter: `url("#${identity}-distortion")`, zIndex: -3 }), frost && _jsx(BackdropLayer, { name: "frost", filter: frost, zIndex: -2 }), _jsx(MaterialPaint, { identity: identity, distortion: distortion, color: material.color, grain: material.grain, grainAmount: material.grainAmount, opacity: material.opacity })] });
|
|
53
|
+
}
|
|
54
|
+
/** Keep refraction and native frost in independent compositor passes. */
|
|
55
|
+
function BackdropLayer({ filter, name, zIndex }) {
|
|
56
|
+
return _jsx("span", { "data-material-backdrop": name, "aria-hidden": "true", style: {
|
|
57
|
+
...layerStyle,
|
|
58
|
+
zIndex,
|
|
59
|
+
backdropFilter: filter,
|
|
60
|
+
WebkitBackdropFilter: filter
|
|
61
|
+
} });
|
|
62
|
+
}
|
package/dist/switch.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SwitchFieldProps } from "react-aria-components";
|
|
2
2
|
import type { ControlOverrides, ControlProps, FieldProps } from "./control.js";
|
|
3
|
-
import type { MaterialOverrides } from "./material.js";
|
|
3
|
+
import type { MaterialOverrides } from "./material-options.js";
|
|
4
4
|
export interface SwitchProps extends Omit<SwitchFieldProps, ControlOverrides | "isReadOnly" | "isSelected" | "defaultSelected">, ControlProps, FieldProps, MaterialOverrides {
|
|
5
5
|
readonly checked?: boolean;
|
|
6
6
|
readonly defaultChecked?: boolean;
|
package/dist/text-control.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TextFieldProps } from "react-aria-components";
|
|
2
2
|
import type { ControlOverrides, ControlProps, FieldProps } from "./control.js";
|
|
3
3
|
import type { RadiusProps } from "./radius.js";
|
|
4
|
-
import type { MaterialOverrides } from "./material.js";
|
|
4
|
+
import type { MaterialOverrides } from "./material-options.js";
|
|
5
5
|
/** Shared contract for native text-entry controls. */
|
|
6
6
|
export interface TextControlProps extends Omit<TextFieldProps, ControlOverrides | "isReadOnly">, ControlProps, FieldProps, RadiusProps, MaterialOverrides {
|
|
7
7
|
readonly placeholder?: string;
|
package/dist/textarea.js
CHANGED
|
@@ -2,12 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import { TextArea as AriaTextarea, TextField } from "react-aria-components";
|
|
4
4
|
import { controlPaint, controlStyle, FieldFeedback, FieldLabel, fieldStyle, useControlTheme } from "./control.js";
|
|
5
|
-
import {
|
|
5
|
+
import { SurfaceField } from "./control-surface.js";
|
|
6
6
|
import FieldStyle, { textControlClass } from "./field-style.js";
|
|
7
7
|
/** A labeled multiline text control, vertically resizable without escaping its width. */
|
|
8
8
|
export const Textarea = forwardRef(function Textarea({ label, description, errorMessage, disabled, readOnly, required, invalid, size, color, radius, style, className, placeholder, rows = 4, material, ...properties }, ref) {
|
|
9
9
|
const theme = useControlTheme({ size, color, radius });
|
|
10
|
-
return _jsxs(TextField, { ...properties, className: className, isDisabled: disabled, isReadOnly: readOnly, isRequired: required, isInvalid: invalid, style: fieldStyle(theme, disabled, style), children: [_jsx(FieldStyle, {}), _jsx(FieldLabel, { label: label }), _jsx(AriaTextarea, { ref: ref, placeholder: placeholder, rows: rows, className: textControlClass, render: (native, state) => _jsx(
|
|
10
|
+
return _jsxs(TextField, { ...properties, className: className, isDisabled: disabled, isReadOnly: readOnly, isRequired: required, isInvalid: invalid, style: fieldStyle(theme, disabled, style), children: [_jsx(FieldStyle, {}), _jsx(FieldLabel, { label: label }), _jsx(AriaTextarea, { ref: ref, placeholder: placeholder, rows: rows, className: textControlClass, render: (native, state) => _jsx(SurfaceField, { material: material, radius: theme.radius, paint: controlPaint(theme, state.isFocused, state.isInvalid, state.isHovered), children: _jsx("textarea", { ...native, style: { ...native.style, borderRadius: "inherit", background: "transparent" } }) }), style: state => ({
|
|
11
11
|
...controlStyle(theme, state.isFocused, state.isInvalid, state.isHovered, state.isFocusVisible),
|
|
12
12
|
height: "auto",
|
|
13
13
|
minHeight: theme.height,
|
package/dist/toggle-indicator.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { motion, useReducedMotion } from "motion/react";
|
|
3
3
|
import { useLocale } from "react-aria-components";
|
|
4
|
-
import { MaterialLayer, useResolvedMaterial } from "./material.js";
|
|
5
|
-
import { SurfaceEdge } from "./surface-edge.js";
|
|
6
4
|
import { controlTransition, visualTransition } from "./motion-style.js";
|
|
5
|
+
import { Surface } from "./surface.js";
|
|
7
6
|
/** Shared paint only. Selection, focus, validation, and native input behavior belong to React Aria. */
|
|
8
7
|
export function ToggleIndicator({ kind, selected, indeterminate = false, focused, invalid = false, hovered = false, pressed = false, theme, material: options }) {
|
|
9
8
|
const reduced = useReducedMotion();
|
|
@@ -16,11 +15,9 @@ export function ToggleIndicator({ kind, selected, indeterminate = false, focused
|
|
|
16
15
|
const switching = kind === "switch";
|
|
17
16
|
const thumbWidth = diameter - 6 + (pressed && !reduced ? 3 : 0);
|
|
18
17
|
const travel = (diameter * 2 - thumbWidth) / 2 - 3;
|
|
19
|
-
|
|
20
|
-
return _jsxs(motion.span, { "aria-hidden": "true", initial: false, animate: { scale: reduced || switching ? 1 : pressed ? 0.94 : hovered ? 1.03 : 1 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration }, style: {
|
|
18
|
+
return _jsx(Surface, { as: motion.span, ...options, color: paint.background, "aria-hidden": "true", initial: false, animate: { scale: reduced || switching ? 1 : pressed ? 0.94 : hovered ? 1.03 : 1 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration }, style: {
|
|
21
19
|
color: paint.color,
|
|
22
20
|
...visualTransition,
|
|
23
|
-
background: "transparent",
|
|
24
21
|
display: "inline-grid",
|
|
25
22
|
placeItems: "center",
|
|
26
23
|
position: "relative",
|
|
@@ -32,12 +29,12 @@ export function ToggleIndicator({ kind, selected, indeterminate = false, focused
|
|
|
32
29
|
border: "none",
|
|
33
30
|
outline: focused ? `2px solid ${theme.foreground}` : "none",
|
|
34
31
|
outlineOffset: 2,
|
|
35
|
-
}, children:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
}, children: switching ? _jsx(motion.span, { initial: false, animate: { width: thumbWidth, x: (selected ? travel : -travel) * (direction === "rtl" ? -1 : 1) }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration }, style: {
|
|
33
|
+
...visualTransition,
|
|
34
|
+
height: diameter - 6, borderRadius: diameter,
|
|
35
|
+
background: paint.color
|
|
36
|
+
} }) : _jsx(motion.span, { initial: false, animate: { opacity: active ? 1 : 0, scale: active ? 1 : 0.7 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration }, style: { display: "grid", placeItems: "center" }, children: kind === "radio" ? _jsx("span", { style: { ...visualTransition, width: diameter / 2, height: diameter / 2, borderRadius: "50%", background: "currentColor" } })
|
|
37
|
+
: _jsxs("svg", { width: diameter - 4, height: diameter - 4, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx(motion.path, { d: "m3 8 3 3 7-7", initial: false, animate: { pathLength: selected && !indeterminate ? 1 : 0, opacity: selected && !indeterminate ? 1 : 0 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration } }), _jsx(motion.path, { d: "M4 8h8", initial: false, animate: { pathLength: indeterminate ? 1 : 0, opacity: indeterminate ? 1 : 0 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration } })] }) }) });
|
|
41
38
|
}
|
|
42
39
|
export function toggleStyle(theme, disabled, readOnly) {
|
|
43
40
|
return {
|
package/package.json
CHANGED
package/dist/control-material.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { MaterialLayer, useResolvedMaterial } from "./material.js";
|
|
3
|
-
import { SurfaceEdge } from "./surface-edge.js";
|
|
4
|
-
/** A native button hosts Material without changing its native contract. */
|
|
5
|
-
export function MaterialButton({ native, paint, material: options }) {
|
|
6
|
-
const { ref, style, children, ...properties } = native;
|
|
7
|
-
const material = useResolvedMaterial({ color: paint.background, material: options });
|
|
8
|
-
return _jsxs("button", { ...properties, ref: ref, style: { ...style, background: "transparent", color: paint.color, position: style?.position ?? "relative", isolation: "isolate" }, children: [_jsx(MaterialLayer, { material: material }), _jsx(SurfaceEdge, { material: material }), children] });
|
|
9
|
-
}
|
|
10
|
-
/** Void text controls use a material host while retaining native sizing. */
|
|
11
|
-
export function MaterialField({ paint, radius, children, material: options }) {
|
|
12
|
-
const material = useResolvedMaterial({ color: paint.background, material: options });
|
|
13
|
-
return _jsxs("span", { style: { color: paint.color, display: "grid", minWidth: 0, borderRadius: radius, position: "relative", isolation: "isolate" }, children: [_jsx(MaterialLayer, { material: material }), _jsx(SurfaceEdge, { material: material }), children] });
|
|
14
|
-
}
|
package/dist/material.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { type MaterialOptions } from "./material-options.js";
|
|
2
|
-
import { type Color } from "./color.js";
|
|
3
|
-
export type { MaterialOptions } from "./material-options.js";
|
|
4
|
-
/** Visual substance without host layout, content, interaction, or shadow. */
|
|
5
|
-
export interface MaterialProps {
|
|
6
|
-
readonly color?: Color;
|
|
7
|
-
readonly material?: MaterialOptions;
|
|
8
|
-
}
|
|
9
|
-
export interface MaterialOverrides {
|
|
10
|
-
readonly material?: MaterialOptions;
|
|
11
|
-
}
|
|
12
|
-
/** Fill the geometry of the nearest positioned host with the resolved material. */
|
|
13
|
-
export declare function Material(options?: MaterialProps): import("react").JSX.Element;
|
|
14
|
-
export declare function useResolvedMaterial({ color, material: options }?: MaterialProps): {
|
|
15
|
-
color: string;
|
|
16
|
-
foreground: string;
|
|
17
|
-
opacity: number;
|
|
18
|
-
backdrop: number;
|
|
19
|
-
grain: number;
|
|
20
|
-
grainAmount: number;
|
|
21
|
-
distortion: number;
|
|
22
|
-
saturation: number;
|
|
23
|
-
};
|
|
24
|
-
export type ResolvedMaterial = ReturnType<typeof useResolvedMaterial>;
|
|
25
|
-
export declare function MaterialLayer({ material }: Readonly<{
|
|
26
|
-
material: ResolvedMaterial;
|
|
27
|
-
}>): import("react").JSX.Element;
|
package/dist/material.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useId } from "react";
|
|
3
|
-
import { useMaterialOptions } from "./material-options.js";
|
|
4
|
-
import { useAppearance, useResolveTheme } from "./appearance-provider.js";
|
|
5
|
-
import { useResolveColor } from "./color.js";
|
|
6
|
-
import { MaterialPaint } from "./material-paint.js";
|
|
7
|
-
import MotionStyle from "./motion-style.js";
|
|
8
|
-
const layerStyle = {
|
|
9
|
-
position: "absolute",
|
|
10
|
-
inset: 0,
|
|
11
|
-
borderRadius: "inherit",
|
|
12
|
-
pointerEvents: "none"
|
|
13
|
-
};
|
|
14
|
-
/** Fill the geometry of the nearest positioned host with the resolved material. */
|
|
15
|
-
export function Material(options = {}) {
|
|
16
|
-
const material = useResolvedMaterial(options);
|
|
17
|
-
return _jsx(MaterialLayer, { material: material });
|
|
18
|
-
}
|
|
19
|
-
export function useResolvedMaterial({ color, material: options } = {}) {
|
|
20
|
-
const appearance = useAppearance();
|
|
21
|
-
const material = useMaterialOptions(options);
|
|
22
|
-
return {
|
|
23
|
-
...material,
|
|
24
|
-
color: useResolveColor(color),
|
|
25
|
-
foreground: useResolveTheme(appearance.colors.foreground)
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
export function MaterialLayer({ material }) {
|
|
29
|
-
const { backdrop, saturation, distortion } = material;
|
|
30
|
-
const frost = [
|
|
31
|
-
backdrop === 0 ? "" : `blur(${backdrop}px)`,
|
|
32
|
-
saturation === 1 ? "" : `saturate(${saturation})`
|
|
33
|
-
].filter(Boolean).join(" ");
|
|
34
|
-
const identity = `phresh-material-${useId().replaceAll(":", "")}`;
|
|
35
|
-
return _jsxs("span", { "data-material": "", "aria-hidden": "true", style: layerStyle, children: [_jsx(MotionStyle, {}), distortion > 0 && _jsx(BackdropLayer, { name: "refraction", filter: `url("#${identity}-distortion")`, zIndex: -3 }), frost && _jsx(BackdropLayer, { name: "frost", filter: frost, zIndex: -2 }), _jsx(MaterialPaint, { identity: identity, distortion: distortion, color: material.color, grain: material.grain, grainAmount: material.grainAmount, opacity: material.opacity })] });
|
|
36
|
-
}
|
|
37
|
-
/** Keep refraction and native frost in independent compositor passes. */
|
|
38
|
-
function BackdropLayer({ filter, name, zIndex }) {
|
|
39
|
-
return _jsx("span", { "data-material-backdrop": name, "aria-hidden": "true", style: {
|
|
40
|
-
...layerStyle,
|
|
41
|
-
zIndex,
|
|
42
|
-
backdropFilter: filter,
|
|
43
|
-
WebkitBackdropFilter: filter
|
|
44
|
-
} });
|
|
45
|
-
}
|