@phreshos/react-ui 0.1.27 → 0.1.29
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 +40 -21
- package/dist/appearance-context.d.ts +47 -16
- package/dist/appearance-context.js +70 -20
- package/dist/appearance.d.ts +6 -0
- package/dist/appearance.js +11 -0
- package/dist/button.js +6 -10
- package/dist/color.d.ts +7 -5
- package/dist/color.js +37 -12
- package/dist/context-menu.d.ts +4 -1
- package/dist/context-menu.js +30 -3
- package/dist/control.d.ts +16 -2
- package/dist/control.js +34 -20
- package/dist/dialog.js +47 -40
- package/dist/direction.d.ts +9 -1
- package/dist/direction.js +34 -10
- package/dist/field-style.js +2 -1
- package/dist/layout.d.ts +1 -1
- package/dist/main.d.ts +4 -3
- package/dist/main.js +3 -2
- package/dist/material-options.d.ts +10 -3
- package/dist/material-options.js +3 -6
- package/dist/material-paint.d.ts +3 -1
- package/dist/material-paint.js +35 -11
- package/dist/menu.js +30 -18
- package/dist/motion-style.d.ts +6 -8
- package/dist/motion-style.js +15 -31
- package/dist/popover.js +13 -12
- package/dist/preferences.d.ts +4 -0
- package/dist/preferences.js +0 -0
- package/dist/radio.js +4 -3
- package/dist/radius.d.ts +1 -1
- package/dist/scroll-area.js +17 -10
- package/dist/select.js +24 -36
- package/dist/shadow-options.d.ts +9 -3
- package/dist/shadow-options.js +3 -6
- package/dist/slider.js +7 -6
- package/dist/spacing.d.ts +1 -1
- package/dist/surface-edge.d.ts +3 -1
- package/dist/surface-edge.js +3 -5
- package/dist/surface.js +24 -19
- package/dist/toggle-indicator.js +2 -5
- package/dist/tooltip.js +16 -15
- package/dist/ui-provider.d.ts +2 -1
- package/dist/ui-provider.js +9 -7
- package/dist/window-header.d.ts +42 -0
- package/dist/window-header.js +113 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ Environment-neutral React components and the visual language of PhreshOS.
|
|
|
8
8
|
|
|
9
9
|
## Role
|
|
10
10
|
|
|
11
|
-
React UI
|
|
12
|
-
|
|
11
|
+
React UI owns reusable visual contracts that accept the corresponding PhreshOS
|
|
12
|
+
System values directly. The Desktop and Programs compose those primitives instead of
|
|
13
13
|
reimplementing material, spacing, color, radius, or interaction behavior.
|
|
14
14
|
|
|
15
15
|
The package does not depend on a Client or Server runtime and does not own
|
|
@@ -25,9 +25,8 @@ applications own composition.
|
|
|
25
25
|
| Bun | `bun add @phreshos/react-ui` |
|
|
26
26
|
| Yarn | `yarn add @phreshos/react-ui` |
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
runtime.
|
|
28
|
+
React and React DOM are peer dependencies because the application and React UI
|
|
29
|
+
must share one React runtime.
|
|
31
30
|
|
|
32
31
|
```tsx
|
|
33
32
|
import { Button, Surface } from "@phreshos/react-ui"
|
|
@@ -37,16 +36,16 @@ import { Button, Surface } from "@phreshos/react-ui"
|
|
|
37
36
|
</Surface>
|
|
38
37
|
```
|
|
39
38
|
|
|
40
|
-
Without a provider, components use
|
|
39
|
+
Without a provider, components use React UI's `defaultAppearance` and reactively
|
|
41
40
|
follow the browser's complete visual preferences. `UIProvider` supplies the
|
|
42
41
|
Appearance, Preferences, and direction explicitly established for a React UI
|
|
43
42
|
subtree. Each omitted property inherits independently from the nearest provider
|
|
44
43
|
or follows its own default resolution. A provider with no properties introduces
|
|
45
44
|
no boundary or behavior.
|
|
46
|
-
React UI also exports
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
React UI also exports `defaultAppearance` for callers that need the value
|
|
46
|
+
explicitly. A complete System Appearance can be passed directly; React UI uses
|
|
47
|
+
the visual fields it owns and ignores additional System fields such as
|
|
48
|
+
wallpapers.
|
|
50
49
|
|
|
51
50
|
React UI is neutral between left-to-right and right-to-left interfaces.
|
|
52
51
|
An explicit `UIProvider` direction creates a boundary that participates in
|
|
@@ -80,11 +79,12 @@ use the shared Surface implementation while retaining their native behavior. Col
|
|
|
80
79
|
are independent inputs. `color` accepts an Appearance color and resting level such
|
|
81
80
|
as `background:base` or `primary:soft`, or a direct CSS color. Surface defaults
|
|
82
81
|
to `background:base`; solid controls default to `default:base`.
|
|
83
|
-
Hover and press derive shades of that fill.
|
|
84
|
-
higher-contrast text from Appearance's background and foreground
|
|
85
|
-
shades keep that choice.
|
|
82
|
+
Hover and press derive shades of that fill. Surface and solid controls choose
|
|
83
|
+
the higher-contrast text from Appearance's background and foreground whenever
|
|
84
|
+
their fill can be evaluated; interaction shades keep that choice.
|
|
86
85
|
`size` accepts `xsmall`, `small`, `medium` (default), `large`, or `xlarge`;
|
|
87
|
-
spacing and radius follow Appearance
|
|
86
|
+
spacing and radius follow Appearance, while typography remains inherited and
|
|
87
|
+
component sizes are relative to the surrounding text. `disabled` prevents activation and focus;
|
|
88
88
|
`pending` prevents activation while retaining focus.
|
|
89
89
|
|
|
90
90
|
```tsx
|
|
@@ -96,7 +96,7 @@ spacing and radius follow Appearance. `disabled` prevents activation and focus;
|
|
|
96
96
|
`Surface` is the material- and shadow-owning element. It renders a `div` by default, while
|
|
97
97
|
`as` selects another React element and preserves that element's native properties
|
|
98
98
|
and ref type. Surface owns its paint, opacity, frost, refraction, grain, edge,
|
|
99
|
-
outer shadow,
|
|
99
|
+
outer shadow, and radius. `radius` accepts a size level, `full`, a
|
|
100
100
|
number in pixels, or a CSS radius and defaults to `medium`.
|
|
101
101
|
|
|
102
102
|
```tsx
|
|
@@ -154,6 +154,25 @@ Native properties and the root ref target the outer Surface. `Panel.Header`
|
|
|
154
154
|
owns the optional leading region, while `Panel.Content` is the independently
|
|
155
155
|
configurable inner Surface.
|
|
156
156
|
|
|
157
|
+
`WindowHeader` provides the layout shared by Desktop-owned and application-owned
|
|
158
|
+
window headers. `Identity` presents the icon and truncating title, `Center` is
|
|
159
|
+
optional flexible space, and `Actions` aligns compact controls at the end. The
|
|
160
|
+
header uses the enclosing Surface rather than creating another material layer.
|
|
161
|
+
Its controls are React UI Buttons; callers connect them to their own window
|
|
162
|
+
operations. Pointer and double-click handlers on the root can implement a drag
|
|
163
|
+
area, while the center and actions keep their interactions separate.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
<WindowHeader active={active} onPointerDown={beginDrag}>
|
|
167
|
+
<WindowHeader.Identity icon={icon} title={title} />
|
|
168
|
+
<WindowHeader.Actions>
|
|
169
|
+
<WindowHeader.Minimize onPress={minimize} />
|
|
170
|
+
<WindowHeader.Maximize maximized={maximized} onPress={toggleMaximize} />
|
|
171
|
+
<WindowHeader.Close onPress={close} />
|
|
172
|
+
</WindowHeader.Actions>
|
|
173
|
+
</WindowHeader>
|
|
174
|
+
```
|
|
175
|
+
|
|
157
176
|
## Overlays
|
|
158
177
|
|
|
159
178
|
Overlay components expose each behavioral role as a named part. Roots own open
|
|
@@ -203,8 +222,8 @@ Blur, distortion, geometry, and Surface host opacity are not transitioned; gradi
|
|
|
203
222
|
and structurally removed effects change directly rather than adding extra
|
|
204
223
|
layers or keeping disabled effects alive.
|
|
205
224
|
|
|
206
|
-
Motion animates toggle presses, selection marks, switch travel
|
|
207
|
-
|
|
225
|
+
Motion animates toggle presses, selection marks, and switch travel. CSS transitions
|
|
226
|
+
animate paint and the Select chevron. Preferences with animations disabled remove spatial
|
|
208
227
|
feedback and make state transitions immediate. Slider values and native input
|
|
209
228
|
behavior are never delayed by visual animation.
|
|
210
229
|
|
|
@@ -256,8 +275,9 @@ import { Input, Textarea, Checkbox, Radio, RadioGroup, Switch, Select, Slider }
|
|
|
256
275
|
<Slider label="Volume" name="volume" defaultValue={50} minValue={0} maxValue={100} step={1} />
|
|
257
276
|
```
|
|
258
277
|
|
|
259
|
-
|
|
260
|
-
|
|
278
|
+
Each control delegates focus, keyboard, form, and selection behavior to its
|
|
279
|
+
accessible behavioral primitive rather than recreating those systems. Select's
|
|
280
|
+
popup uses Surface defaults. Toggle indicators use Motion internally and respect
|
|
261
281
|
animation preferences. The preview Program demonstrates each input's
|
|
262
282
|
sizes, colors, state, and interaction without overriding its visual defaults.
|
|
263
283
|
|
|
@@ -279,8 +299,7 @@ the committed dependency graph without local package substitutions.
|
|
|
279
299
|
|
|
280
300
|
## Related repositories
|
|
281
301
|
|
|
282
|
-
- [`@phreshos/core`](https://github.com/PhreshOS/core) owns
|
|
283
|
-
and the shared values interpreted here.
|
|
302
|
+
- [`@phreshos/core`](https://github.com/PhreshOS/core) owns the System contracts.
|
|
284
303
|
- [`@phreshos/react`](https://github.com/PhreshOS/react) owns runtime-neutral
|
|
285
304
|
React state adaptation.
|
|
286
305
|
- [PhreshOS System](https://github.com/PhreshOS/system) composes the visual
|
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
import { type Appearance, type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
desktopWallpaper: ThemedValue<string | null>;
|
|
11
|
-
}>>;
|
|
12
|
-
export declare const PreferencesContext: import("react").Context<Readonly<{
|
|
13
|
-
theme: import("@phreshos/core").Theme;
|
|
14
|
-
animations: boolean;
|
|
15
|
-
}> | null>;
|
|
16
|
-
/** Returns the nearest unresolved Appearance, or Core's complete default. */
|
|
1
|
+
import { type Appearance, type ThemedValue } from "./appearance.js";
|
|
2
|
+
import type { Preferences } from "./preferences.js";
|
|
3
|
+
export declare const AppearanceContext: import("react").Context<Appearance>;
|
|
4
|
+
export interface PreferencesSource {
|
|
5
|
+
readonly getSnapshot: () => Preferences;
|
|
6
|
+
readonly getServerSnapshot: () => Preferences;
|
|
7
|
+
readonly subscribe: (change: () => void) => () => void;
|
|
8
|
+
}
|
|
9
|
+
/** Returns the nearest unresolved appearance, or React UI's default. */
|
|
17
10
|
export declare function useAppearance(): Appearance;
|
|
18
11
|
/** Returns the nearest complete Preferences, or reactively follows the browser. */
|
|
19
12
|
export declare function usePreferences(): Preferences;
|
|
@@ -21,3 +14,41 @@ export declare function usePreferences(): Preferences;
|
|
|
21
14
|
export declare function useBrowserPreferences(): Preferences;
|
|
22
15
|
/** Selects the active branch of one complete ThemedValue. */
|
|
23
16
|
export declare function useThemedValue<Value>(value: ThemedValue<Value>): Value;
|
|
17
|
+
/** Resolves one coherent Appearance snapshot for internal component derivation. */
|
|
18
|
+
export declare function useResolvedAppearance(): {
|
|
19
|
+
appearance: Appearance;
|
|
20
|
+
preferences: Preferences;
|
|
21
|
+
colors: Readonly<{
|
|
22
|
+
background: string;
|
|
23
|
+
foreground: string;
|
|
24
|
+
default: string;
|
|
25
|
+
primary: string;
|
|
26
|
+
secondary: string;
|
|
27
|
+
success: string;
|
|
28
|
+
warning: string;
|
|
29
|
+
danger: string;
|
|
30
|
+
info: string;
|
|
31
|
+
}>;
|
|
32
|
+
material: Readonly<{
|
|
33
|
+
grain: number;
|
|
34
|
+
grainAmount: number;
|
|
35
|
+
backdrop: number;
|
|
36
|
+
opacity: number;
|
|
37
|
+
distortion: number;
|
|
38
|
+
saturation: number;
|
|
39
|
+
}>;
|
|
40
|
+
shadow: Readonly<{
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
blur: number;
|
|
44
|
+
spread: number;
|
|
45
|
+
opacity: number;
|
|
46
|
+
}>;
|
|
47
|
+
transaction: Readonly<{
|
|
48
|
+
duration: number;
|
|
49
|
+
easing: import("@phreshos/core").Easing;
|
|
50
|
+
}>;
|
|
51
|
+
};
|
|
52
|
+
/** Creates a stable source for a complete explicitly supplied Preferences value. */
|
|
53
|
+
export declare function fixedPreferencesSource(preferences: Preferences): PreferencesSource;
|
|
54
|
+
export declare const PreferencesContext: import("react").Context<PreferencesSource>;
|
|
@@ -1,41 +1,91 @@
|
|
|
1
1
|
import { createContext, useContext, useMemo, useSyncExternalStore } from "react";
|
|
2
|
-
import { defaultAppearance } from "
|
|
2
|
+
import { defaultAppearance } from "./appearance.js";
|
|
3
3
|
export const AppearanceContext = createContext(defaultAppearance);
|
|
4
|
-
|
|
5
|
-
/** Returns the nearest unresolved Appearance, or Core's complete default. */
|
|
4
|
+
/** Returns the nearest unresolved appearance, or React UI's default. */
|
|
6
5
|
export function useAppearance() {
|
|
7
6
|
return useContext(AppearanceContext);
|
|
8
7
|
}
|
|
9
8
|
/** Returns the nearest complete Preferences, or reactively follows the browser. */
|
|
10
9
|
export function usePreferences() {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
return preferences ?? browserPreferences;
|
|
10
|
+
const source = useContext(PreferencesContext);
|
|
11
|
+
return useSyncExternalStore(source.subscribe, source.getSnapshot, source.getServerSnapshot);
|
|
14
12
|
}
|
|
15
13
|
/** Reactively reads the browser's complete visual preferences. */
|
|
16
14
|
export function useBrowserPreferences() {
|
|
17
|
-
|
|
18
|
-
const animations = !useMediaPreference(reducedMotionQuery);
|
|
19
|
-
return useMemo(() => ({ theme, animations }), [animations, theme]);
|
|
15
|
+
return useSyncExternalStore(browserPreferencesSource.subscribe, browserPreferencesSource.getSnapshot, browserPreferencesSource.getServerSnapshot);
|
|
20
16
|
}
|
|
21
17
|
/** Selects the active branch of one complete ThemedValue. */
|
|
22
18
|
export function useThemedValue(value) {
|
|
23
19
|
return value[usePreferences().theme];
|
|
24
20
|
}
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
/** Resolves one coherent Appearance snapshot for internal component derivation. */
|
|
22
|
+
export function useResolvedAppearance() {
|
|
23
|
+
const appearance = useAppearance();
|
|
24
|
+
const preferences = usePreferences();
|
|
25
|
+
return useMemo(() => ({
|
|
26
|
+
appearance,
|
|
27
|
+
preferences,
|
|
28
|
+
colors: appearance.colors[preferences.theme],
|
|
29
|
+
material: appearance.material[preferences.theme],
|
|
30
|
+
shadow: appearance.shadow[preferences.theme],
|
|
31
|
+
transaction: appearance.transaction
|
|
32
|
+
}), [appearance, preferences]);
|
|
27
33
|
}
|
|
28
|
-
|
|
34
|
+
/** Creates a stable source for a complete explicitly supplied Preferences value. */
|
|
35
|
+
export function fixedPreferencesSource(preferences) {
|
|
36
|
+
return {
|
|
37
|
+
getSnapshot: () => preferences,
|
|
38
|
+
getServerSnapshot: () => preferences,
|
|
39
|
+
subscribe: emptySubscription
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const defaultPreferences = Object.freeze({ theme: "light", animations: true });
|
|
43
|
+
const subscribers = new Set();
|
|
44
|
+
let darkPreference;
|
|
45
|
+
let reducedMotionPreference;
|
|
46
|
+
let browserSnapshot = defaultPreferences;
|
|
47
|
+
function readBrowserPreferences() {
|
|
29
48
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function")
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
return defaultPreferences;
|
|
50
|
+
darkPreference ??= window.matchMedia(darkThemeQuery);
|
|
51
|
+
reducedMotionPreference ??= window.matchMedia(reducedMotionQuery);
|
|
52
|
+
const theme = darkPreference.matches ? "dark" : "light";
|
|
53
|
+
const animations = !reducedMotionPreference.matches;
|
|
54
|
+
if (browserSnapshot.theme !== theme || browserSnapshot.animations !== animations) {
|
|
55
|
+
browserSnapshot = Object.freeze({ theme, animations });
|
|
56
|
+
}
|
|
57
|
+
return browserSnapshot;
|
|
58
|
+
}
|
|
59
|
+
function subscribeBrowserPreferences(change) {
|
|
60
|
+
subscribers.add(change);
|
|
61
|
+
readBrowserPreferences();
|
|
62
|
+
if (subscribers.size === 1) {
|
|
63
|
+
darkPreference?.addEventListener("change", publishBrowserPreferences);
|
|
64
|
+
reducedMotionPreference?.addEventListener("change", publishBrowserPreferences);
|
|
65
|
+
}
|
|
66
|
+
return () => {
|
|
67
|
+
subscribers.delete(change);
|
|
68
|
+
if (subscribers.size !== 0)
|
|
69
|
+
return;
|
|
70
|
+
darkPreference?.removeEventListener("change", publishBrowserPreferences);
|
|
71
|
+
reducedMotionPreference?.removeEventListener("change", publishBrowserPreferences);
|
|
72
|
+
darkPreference = undefined;
|
|
73
|
+
reducedMotionPreference = undefined;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function publishBrowserPreferences() {
|
|
77
|
+
readBrowserPreferences();
|
|
78
|
+
for (const subscriber of subscribers)
|
|
79
|
+
subscriber();
|
|
34
80
|
}
|
|
35
|
-
function
|
|
36
|
-
return
|
|
37
|
-
&& typeof window.matchMedia === "function"
|
|
38
|
-
&& window.matchMedia(query).matches);
|
|
81
|
+
function emptySubscription() {
|
|
82
|
+
return () => undefined;
|
|
39
83
|
}
|
|
84
|
+
const browserPreferencesSource = {
|
|
85
|
+
getSnapshot: readBrowserPreferences,
|
|
86
|
+
getServerSnapshot: () => defaultPreferences,
|
|
87
|
+
subscribe: subscribeBrowserPreferences
|
|
88
|
+
};
|
|
89
|
+
export const PreferencesContext = createContext(browserPreferencesSource);
|
|
40
90
|
const darkThemeQuery = "(prefers-color-scheme: dark)";
|
|
41
91
|
const reducedMotionQuery = "(prefers-reduced-motion: reduce)";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { appearanceLimits, type Appearance as SystemAppearance, type AppearanceColor, type AppearanceColors, type AppearanceMaterial, type AppearanceRange, type AppearanceShadow, type AppearanceTransaction, type Easing, type ThemedValue } from "@phreshos/core";
|
|
2
|
+
/** Complete visual input consumed by React UI. */
|
|
3
|
+
export type Appearance = Pick<SystemAppearance, "colors" | "spacing" | "radius" | "shadow" | "material" | "transaction">;
|
|
4
|
+
export { appearanceLimits, type AppearanceColor, type AppearanceColors, type AppearanceMaterial, type AppearanceRange, type AppearanceShadow, type AppearanceTransaction, type Easing, type ThemedValue };
|
|
5
|
+
/** React UI's default visual input, derived from the System default. */
|
|
6
|
+
export declare const defaultAppearance: Appearance;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { appearanceLimits, defaultAppearance as systemDefaultAppearance } from "@phreshos/core";
|
|
2
|
+
export { appearanceLimits };
|
|
3
|
+
/** React UI's default visual input, derived from the System default. */
|
|
4
|
+
export const defaultAppearance = Object.freeze({
|
|
5
|
+
colors: systemDefaultAppearance.colors,
|
|
6
|
+
spacing: systemDefaultAppearance.spacing,
|
|
7
|
+
radius: systemDefaultAppearance.radius,
|
|
8
|
+
shadow: systemDefaultAppearance.shadow,
|
|
9
|
+
material: systemDefaultAppearance.material,
|
|
10
|
+
transaction: systemDefaultAppearance.transaction
|
|
11
|
+
});
|
package/dist/button.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 { Button as AriaButton } from "react-aria-components";
|
|
4
|
-
import {
|
|
4
|
+
import { controlOpacity, 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
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) {
|
|
@@ -13,14 +13,11 @@ export const Button = forwardRef(function Button({ children, color, disabled = f
|
|
|
13
13
|
isHovered,
|
|
14
14
|
isPressed,
|
|
15
15
|
pending,
|
|
16
|
-
size,
|
|
17
16
|
style
|
|
18
17
|
}), children: children });
|
|
19
18
|
});
|
|
20
|
-
function buttonStyle({ theme, disabled, isFocusVisible, isHovered, isPressed, pending,
|
|
21
|
-
const fontSize = controlFontSizes[size];
|
|
19
|
+
function buttonStyle({ theme, disabled, isFocusVisible, isHovered, isPressed, pending, style }) {
|
|
22
20
|
const { spacing, focusColor } = theme;
|
|
23
|
-
const height = Math.max(24, 24 + spacing);
|
|
24
21
|
const interactive = !disabled && !pending;
|
|
25
22
|
const paint = buttonPaint(theme, interactive, isHovered, isPressed);
|
|
26
23
|
return {
|
|
@@ -34,20 +31,19 @@ function buttonStyle({ theme, disabled, isFocusVisible, isHovered, isPressed, pe
|
|
|
34
31
|
justifyContent: "center",
|
|
35
32
|
flexShrink: 0,
|
|
36
33
|
minWidth: 0,
|
|
37
|
-
height,
|
|
34
|
+
height: theme.height,
|
|
38
35
|
paddingBlock: 0,
|
|
39
36
|
paddingInline: Math.max(8, spacing),
|
|
40
|
-
gap:
|
|
37
|
+
gap: theme.gap,
|
|
41
38
|
border: "none",
|
|
42
39
|
borderRadius: theme.radius,
|
|
43
40
|
outline: isFocusVisible ? `1px solid ${focusColor}` : "none",
|
|
44
41
|
outlineOffset: 1,
|
|
45
42
|
...paint,
|
|
46
|
-
opacity: disabled ?
|
|
43
|
+
opacity: disabled ? controlOpacity.disabled : pending ? controlOpacity.pending : 1,
|
|
47
44
|
cursor: disabled ? "not-allowed" : pending ? "progress" : "pointer",
|
|
48
45
|
font: "inherit",
|
|
49
|
-
fontSize,
|
|
50
|
-
fontWeight: 550,
|
|
46
|
+
fontSize: theme.fontSize,
|
|
51
47
|
lineHeight: 1,
|
|
52
48
|
textAlign: "center",
|
|
53
49
|
textDecoration: "none",
|
package/dist/color.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppearanceColor } from "
|
|
1
|
+
import type { AppearanceColor, AppearanceColors } from "./appearance.js";
|
|
2
2
|
/** A visual treatment derived from one concrete CSS color. */
|
|
3
3
|
export type ColorLevel = "subtle" | "soft" | "base" | "strong" | "intense";
|
|
4
4
|
/** Every visual treatment derived from one concrete CSS color. */
|
|
@@ -14,10 +14,10 @@ export declare function color(value: string): ColorScale;
|
|
|
14
14
|
export declare function resolveColorLevel(value: string, level: ColorLevel): string;
|
|
15
15
|
/** Returns the complete visual treatments derived from one concrete color. */
|
|
16
16
|
export declare function useColor(value: string): ColorScale;
|
|
17
|
-
/** Resolves a semantic
|
|
18
|
-
export declare function
|
|
19
|
-
/** Resolves
|
|
20
|
-
export declare function
|
|
17
|
+
/** Resolves a semantic color from an already selected Appearance branch. */
|
|
18
|
+
export declare function resolveColor(value: Color | undefined, colors: AppearanceColors): string;
|
|
19
|
+
/** Resolves opaque control paint from an already selected Appearance branch. */
|
|
20
|
+
export declare function resolveSolidColor(value: Color, colors: AppearanceColors): string;
|
|
21
21
|
/** Applies material opacity without restricting the source CSS color syntax. */
|
|
22
22
|
export declare function colorOpacity(value: string, opacity: number): string;
|
|
23
23
|
/** Reads perceptual lightness from a resolved CSS color. */
|
|
@@ -33,6 +33,8 @@ export declare function opaqueColor(value: string): string;
|
|
|
33
33
|
export declare function colorShade(value: string, amount: number): string;
|
|
34
34
|
/** Chooses only from the two Appearance colors, against the actual painted fill. */
|
|
35
35
|
export declare function onColor(fill: string, background: string, foreground: string): string;
|
|
36
|
+
/** Chooses readable content when the fill can be evaluated, otherwise preserves Appearance foreground. */
|
|
37
|
+
export declare function contrastingColor(value: Color, colors: AppearanceColors): string;
|
|
36
38
|
/** Solid controls start at base; interaction shades preserve its text choice. */
|
|
37
39
|
export declare function solidColors(base: string, background: string, foreground: string): {
|
|
38
40
|
rest: {
|
package/dist/color.js
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import ColorSpace from "colorjs.io/src/ColorSpace.js";
|
|
3
|
+
import { mix } from "colorjs.io/src/interpolation.js";
|
|
4
|
+
import parse from "colorjs.io/src/parse.js";
|
|
5
|
+
import serialize from "colorjs.io/src/serialize.js";
|
|
6
|
+
import to from "colorjs.io/src/to.js";
|
|
7
|
+
import toGamut from "colorjs.io/src/toGamut.js";
|
|
8
|
+
import contrastWCAG21 from "colorjs.io/src/contrast/WCAG21.js";
|
|
9
|
+
import sRGB from "colorjs.io/src/spaces/srgb.js";
|
|
10
|
+
import sRGB_Linear from "colorjs.io/src/spaces/srgb-linear.js";
|
|
11
|
+
import HSL from "colorjs.io/src/spaces/hsl.js";
|
|
12
|
+
import HWB from "colorjs.io/src/spaces/hwb.js";
|
|
13
|
+
import Lab from "colorjs.io/src/spaces/lab.js";
|
|
14
|
+
import LCH from "colorjs.io/src/spaces/lch.js";
|
|
15
|
+
import OKLab from "colorjs.io/src/spaces/oklab.js";
|
|
16
|
+
import OKLCH from "colorjs.io/src/spaces/oklch.js";
|
|
17
|
+
import P3 from "colorjs.io/src/spaces/p3.js";
|
|
18
|
+
import A98RGB from "colorjs.io/src/spaces/a98rgb.js";
|
|
19
|
+
import ProPhoto from "colorjs.io/src/spaces/prophoto.js";
|
|
20
|
+
import REC_2020 from "colorjs.io/src/spaces/rec2020.js";
|
|
21
|
+
import XYZ_D50 from "colorjs.io/src/spaces/xyz-d50.js";
|
|
22
|
+
import XYZ_D65 from "colorjs.io/src/spaces/xyz-d65.js";
|
|
4
23
|
// Register the CSS color spaces, without bundling unrelated color-model APIs.
|
|
5
24
|
for (const space of [sRGB, sRGB_Linear, HSL, HWB, Lab, LCH, OKLab, OKLCH, P3, A98RGB, ProPhoto, REC_2020, XYZ_D50, XYZ_D65])
|
|
6
25
|
ColorSpace.register(space);
|
|
@@ -40,17 +59,14 @@ export function resolveColorLevel(value, level) {
|
|
|
40
59
|
export function useColor(value) {
|
|
41
60
|
return useMemo(() => color(value), [value]);
|
|
42
61
|
}
|
|
43
|
-
/** Resolves a semantic
|
|
44
|
-
export function
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
return semantic ? color(semantic.source)[semantic.level] : value;
|
|
62
|
+
/** Resolves a semantic color from an already selected Appearance branch. */
|
|
63
|
+
export function resolveColor(value, colors) {
|
|
64
|
+
const resolved = value ?? defaultColor;
|
|
65
|
+
const semantic = parseSemanticColor(resolved, colors);
|
|
66
|
+
return semantic ? color(semantic.source)[semantic.level] : resolved;
|
|
49
67
|
}
|
|
50
|
-
/** Resolves
|
|
51
|
-
export function
|
|
52
|
-
const appearance = useAppearance();
|
|
53
|
-
const colors = useThemedValue(appearance.colors);
|
|
68
|
+
/** Resolves opaque control paint from an already selected Appearance branch. */
|
|
69
|
+
export function resolveSolidColor(value, colors) {
|
|
54
70
|
const semantic = parseSemanticColor(value, colors);
|
|
55
71
|
return semantic ? resolveColorLevel(semantic.source, semantic.level) : opaqueColor(value);
|
|
56
72
|
}
|
|
@@ -88,6 +104,15 @@ export function onColor(fill, background, foreground) {
|
|
|
88
104
|
const second = opaqueColor(foreground);
|
|
89
105
|
return contrastWCAG21(fill, first) > contrastWCAG21(fill, second) ? first : second;
|
|
90
106
|
}
|
|
107
|
+
/** Chooses readable content when the fill can be evaluated, otherwise preserves Appearance foreground. */
|
|
108
|
+
export function contrastingColor(value, colors) {
|
|
109
|
+
try {
|
|
110
|
+
return onColor(resolveSolidColor(value, colors), colors.background, colors.foreground);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return colors.foreground;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
91
116
|
/** Solid controls start at base; interaction shades preserve its text choice. */
|
|
92
117
|
export function solidColors(base, background, foreground) {
|
|
93
118
|
const fill = resolveColorLevel(base, "base");
|
package/dist/context-menu.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { ComponentProps } from "react";
|
|
2
2
|
import { Pressable as AriaPressable } from "react-aria-components";
|
|
3
3
|
import type { MenuTriggerProps as AriaMenuTriggerProps } from "react-aria-components";
|
|
4
|
+
import { type PopoverContentProps } from "./popover.js";
|
|
4
5
|
export type ContextMenuRootProps = Omit<AriaMenuTriggerProps, "trigger">;
|
|
5
6
|
export declare function ContextMenuRoot(properties: ContextMenuRootProps): import("react").JSX.Element;
|
|
6
7
|
export type ContextMenuTriggerProps = ComponentProps<typeof AriaPressable>;
|
|
7
8
|
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>>;
|
|
9
|
+
export type ContextMenuContentProps = Omit<PopoverContentProps, "isNonModal">;
|
|
10
|
+
export declare const ContextMenuContent: import("react").ForwardRefExoticComponent<ContextMenuContentProps & import("react").RefAttributes<HTMLElement>>;
|
|
8
11
|
/** A context-requested overlay sharing the same Menu contract as DropdownMenu. */
|
|
9
12
|
export declare const ContextMenu: typeof ContextMenuRoot & {
|
|
10
13
|
Root: typeof ContextMenuRoot;
|
|
11
14
|
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<
|
|
15
|
+
Content: import("react").ForwardRefExoticComponent<ContextMenuContentProps & import("react").RefAttributes<HTMLElement>>;
|
|
13
16
|
Menu: import("./menu.js").MenuRootComponent & {
|
|
14
17
|
Root: import("./menu.js").MenuRootComponent;
|
|
15
18
|
Item: import("./menu.js").MenuItemComponent;
|
package/dist/context-menu.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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";
|
|
2
|
+
import { forwardRef, useCallback, useContext, useEffect, useRef } from "react";
|
|
3
|
+
import { MenuTrigger as AriaMenuTrigger, OverlayTriggerStateContext, Pressable as AriaPressable } from "react-aria-components";
|
|
4
4
|
import { Menu } from "./menu.js";
|
|
5
5
|
import { PopoverContent } from "./popover.js";
|
|
6
6
|
export function ContextMenuRoot(properties) {
|
|
@@ -9,10 +9,37 @@ export function ContextMenuRoot(properties) {
|
|
|
9
9
|
export const ContextMenuTrigger = forwardRef(function ContextMenuTrigger(properties, ref) {
|
|
10
10
|
return _jsx(AriaPressable, { ...properties, ref: ref });
|
|
11
11
|
});
|
|
12
|
+
function setRef(ref, value) {
|
|
13
|
+
if (typeof ref === "function")
|
|
14
|
+
ref(value);
|
|
15
|
+
else if (ref)
|
|
16
|
+
ref.current = value;
|
|
17
|
+
}
|
|
18
|
+
export const ContextMenuContent = forwardRef(function ContextMenuContent(properties, forwardedRef) {
|
|
19
|
+
const state = useContext(OverlayTriggerStateContext);
|
|
20
|
+
const contentRef = useRef(null);
|
|
21
|
+
const ref = useCallback((element) => {
|
|
22
|
+
contentRef.current = element;
|
|
23
|
+
setRef(forwardedRef, element);
|
|
24
|
+
}, [forwardedRef]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!state?.isOpen)
|
|
27
|
+
return;
|
|
28
|
+
function dismiss(event) {
|
|
29
|
+
const target = event.target;
|
|
30
|
+
if (target instanceof Node && contentRef.current?.contains(target))
|
|
31
|
+
return;
|
|
32
|
+
state?.close();
|
|
33
|
+
}
|
|
34
|
+
document.addEventListener("pointerdown", dismiss, true);
|
|
35
|
+
return () => document.removeEventListener("pointerdown", dismiss, true);
|
|
36
|
+
}, [state]);
|
|
37
|
+
return _jsx(PopoverContent, { ...properties, ref: ref, isNonModal: true });
|
|
38
|
+
});
|
|
12
39
|
/** A context-requested overlay sharing the same Menu contract as DropdownMenu. */
|
|
13
40
|
export const ContextMenu = Object.assign(ContextMenuRoot, {
|
|
14
41
|
Root: ContextMenuRoot,
|
|
15
42
|
Trigger: ContextMenuTrigger,
|
|
16
|
-
Content:
|
|
43
|
+
Content: ContextMenuContent,
|
|
17
44
|
Menu
|
|
18
45
|
});
|
package/dist/control.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type RadiusProps } from "./radius.js";
|
|
|
3
3
|
import { type ScaleLevel } from "./scale.js";
|
|
4
4
|
import { solidColors } from "./color.js";
|
|
5
5
|
import { type Color } from "./color.js";
|
|
6
|
+
import type { Transition } from "motion/react";
|
|
6
7
|
/** Semantic Appearance colors accepted by interactive controls. */
|
|
7
8
|
export type ControlColor = Color;
|
|
8
9
|
export interface ControlProps {
|
|
@@ -22,10 +23,22 @@ export interface FieldProps {
|
|
|
22
23
|
}
|
|
23
24
|
/** Names owned by our contract, not a second set of primitive-specific aliases. */
|
|
24
25
|
export type ControlOverrides = keyof ControlProps | keyof FieldProps | "children" | "isDisabled" | "isRequired" | "isInvalid";
|
|
25
|
-
export declare const controlFontSizes: Readonly<Record<ScaleLevel,
|
|
26
|
+
export declare const controlFontSizes: Readonly<Record<ScaleLevel, CSSProperties["fontSize"]>>;
|
|
27
|
+
/** Shared interaction attenuation, derived consistently across control families. */
|
|
28
|
+
export declare const controlOpacity: Readonly<{
|
|
29
|
+
disabled: 0.46;
|
|
30
|
+
pending: 0.68;
|
|
31
|
+
secondary: 0.7;
|
|
32
|
+
placeholder: 0.6;
|
|
33
|
+
separator: 0.14;
|
|
34
|
+
}>;
|
|
35
|
+
/** Shared emphasis used by labels and control-owned headings. */
|
|
36
|
+
export declare const controlFontWeight = 600;
|
|
26
37
|
type SolidColors = ReturnType<typeof solidColors>;
|
|
27
38
|
export interface ControlTheme {
|
|
28
39
|
readonly transition: CSSProperties;
|
|
40
|
+
readonly motionTransition: Transition;
|
|
41
|
+
readonly animations: boolean;
|
|
29
42
|
readonly spacing: number;
|
|
30
43
|
readonly foreground: string;
|
|
31
44
|
readonly background: string;
|
|
@@ -38,7 +51,8 @@ export interface ControlTheme {
|
|
|
38
51
|
readonly danger: SolidColors;
|
|
39
52
|
};
|
|
40
53
|
readonly radius: CSSProperties["borderRadius"];
|
|
41
|
-
readonly fontSize:
|
|
54
|
+
readonly fontSize: CSSProperties["fontSize"];
|
|
55
|
+
readonly indicatorSize: number;
|
|
42
56
|
readonly height: number;
|
|
43
57
|
readonly gap: number;
|
|
44
58
|
}
|