@phreshos/react-ui 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2026 Zohayr SLILEH
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # `@phreshos/react-ui`
2
+
3
+ A React component library for coherent PhreshOS Program interfaces.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @phreshos/react-ui @phreshos/core react react-dom
9
+ ```
10
+
11
+ React UI accepts an explicit `ThemeProperties` snapshot and does not require a
12
+ running PhreshOS environment:
13
+
14
+ ```tsx
15
+ import { standardTheme } from "@phreshos/core"
16
+ import { Button, Flex, ThemeProvider } from "@phreshos/react-ui"
17
+
18
+ function Example() {
19
+ return <ThemeProvider theme={standardTheme}>
20
+ <Flex align="center" gap="small">
21
+ <Button onPress={() => console.log("save")}>Save</Button>
22
+ </Flex>
23
+ </ThemeProvider>
24
+ }
25
+ ```
26
+
27
+ ## Package status
28
+
29
+ This package is one component of a larger architecture that remains under
30
+ active testing. Its public surface is intentionally small and will grow only
31
+ as component contracts are established. It is usable outside a running
32
+ system, though its standard Theme contract comes from `@phreshos/core`.
33
+
34
+ The library is being built up from behavior contracts rather than from a
35
+ primitive dependency's component catalog. `Button` uses React Aria Components
36
+ for normalized pointer, keyboard, focus, disabled, and pending behavior,
37
+ without exposing that library as the public design language. Components still
38
+ under evaluation may compare React Aria Components against Base UI privately,
39
+ in tests.
40
+
41
+ The library's future icon language has a tree-shakeable public subpath,
42
+ `@phreshos/react-ui/icons`. That subpath deliberately exports nothing until an
43
+ icon source and its contracts have been selected.
44
+
45
+ `Grid` and `Flex` are appearance-neutral layout primitives. They preserve
46
+ native element properties, styles, and refs while naming the layout decisions
47
+ that recur throughout an interface:
48
+
49
+ ```tsx
50
+ <Grid columns="repeat(auto-fit, minmax(12rem, 1fr))" gap="1rem">
51
+ ...
52
+ </Grid>
53
+
54
+ <Flex align="center" justify="between" gap={12} wrap>
55
+ ...
56
+ </Flex>
57
+ ```
58
+
59
+ Numeric gaps are expressed in pixels. Grid dimensions may be positive integer
60
+ counts or native CSS track expressions, leaving responsive behavior to CSS
61
+ rather than introducing a second breakpoint system.
62
+
63
+ Inside a `ThemeProvider`, React UI derives its own spacing levels from the
64
+ Theme's concrete default spacing:
65
+
66
+ ```tsx
67
+ <Flex gap="small">...</Flex>
68
+ <Grid gap="large">...</Grid>
69
+ ```
70
+
71
+ Structures that own native spacing pass the explicit Theme value to the
72
+ general React SDK hook instead:
73
+
74
+ ```tsx
75
+ import { useScale } from "@phreshos/react"
76
+
77
+ const spacing = useScale(theme.spacing)
78
+
79
+ <section style={{ gap: spacing.large }} />
80
+ ```
81
+
82
+ Explicit values such as `4rem` are used directly — passing them through a
83
+ Theme hook would perform no additional work.
84
+
85
+ The Theme stores unrestricted CSS background, foreground, and accent sources.
86
+ Core derives the fixed `subtle`, `soft`, `base`, `strong`, and `intense`
87
+ treatments from any supplied color, preserving the value exactly at `base`.
88
+ `useColor(value)` in the React SDK memoizes that calculation without
89
+ implicitly choosing a Theme property. CSS performs the nearby mixing in
90
+ OKLCH, so React UI does not own or persist a parallel palette of its own:
91
+
92
+ ```tsx
93
+ import { useColor } from "@phreshos/react"
94
+
95
+ const colors = useColor(theme.accent)
96
+
97
+ <strong style={{ color: colors.strong }} />
98
+ ```
99
+
100
+ `GlassSurface` is the shared translucent material. The Theme supplies its
101
+ background, foreground, and concrete default values for distortion, blur,
102
+ saturation, brightness, and material opacity. The component derives its tint
103
+ from the background and applies the foreground to its content, while accent
104
+ remains independent for emphasis and interaction. The surface may also derive
105
+ a small or large treatment from each numeric default without turning those
106
+ levels into system state.
107
+
108
+ Derived opacity is capped at thirty percent and never fades the surface's
109
+ content. Layout, spacing, radius, and external elevation remain ordinary
110
+ container concerns:
111
+
112
+ ```tsx
113
+ <GlassSurface className="rounded-xl p-3">
114
+ ...
115
+ </GlassSurface>
116
+
117
+ <GlassSurface color="soft" distortion="large" blur="small" opacity="medium">
118
+ ...
119
+ </GlassSurface>
120
+ ```
121
+
122
+ Shape-owning components accept the shared `Radius` value directly. Semantic
123
+ levels are derived from the Theme's concrete radius through the same
124
+ `scale()` rule used for spacing, while numbers and CSS values remain explicit
125
+ overrides:
126
+
127
+ ```tsx
128
+ <GlassSurface radius="large">...</GlassSurface>
129
+ <GlassSurface radius="2rem">...</GlassSurface>
130
+ ```
131
+
132
+ Structures whose native element owns the shape derive from the explicit
133
+ radius value through the same general React SDK hook:
134
+
135
+ ```tsx
136
+ import { useScale } from "@phreshos/react"
137
+
138
+ const radius = useScale(theme.radius)
139
+
140
+ <section style={{ borderRadius: radius.large }} />
141
+ ```
142
+
143
+ `Button` is the library's first interactive primitive. Its translucent
144
+ control treatment matches the desktop's Start and sign-out controls, letting
145
+ the surrounding Theme material remain visible. It derives spacing and radius
146
+ from the Theme while keeping a single activation path across pointer, Enter,
147
+ and Space input:
148
+
149
+ ```tsx
150
+ <Button onPress={save}>Save</Button>
151
+ <Button size="large" pending>Saving</Button>
152
+ <Button disabled>Unavailable</Button>
153
+ ```
154
+
155
+ Pending Buttons remain focusable but cannot activate. Disabled Buttons leave
156
+ the focus order entirely. The native element defaults to `type="button"`, so
157
+ placing it inside a form never triggers an accidental submission.
158
+
159
+ `ThemeProvider` accepts a plain `ThemeProperties` snapshot, so the library
160
+ remains usable without either environment SDK. A Program can adapt its
161
+ observable Host value at the application boundary:
162
+
163
+ ```tsx
164
+ import { HostProvider, useHostTheme } from "@phreshos/react"
165
+ import { ThemeProvider } from "@phreshos/react-ui"
166
+
167
+ function ThemedApplication({ children }) {
168
+ const theme = useHostTheme()
169
+
170
+ return <ThemeProvider theme={theme}>{children}</ThemeProvider>
171
+ }
172
+
173
+ <HostProvider provide={["theme"]} fallback={null}>
174
+ <ThemedApplication>{children}</ThemedApplication>
175
+ </HostProvider>
176
+ ```
177
+
178
+ ## Standing requirements
179
+
180
+ - Components preserve one recognizable visual identity.
181
+ - Props that select a semantic treatment accept only the values documented by
182
+ that component. Explicit native styles and supported CSS spacing, radius,
183
+ and color values remain available where the component contract allows them.
184
+ - `ThemeProvider` requires an explicit `theme` prop; it never discovers an
185
+ environment SDK or silently selects a global theme.
186
+ - The provider applies a replacement `theme` value immediately to its
187
+ descendants.
188
+ - Providers are scoped and nestable. The nearest `ThemeProvider` supplies the
189
+ complete theme for its descendants without affecting its parent or
190
+ siblings.
191
+ - Accessibility, keyboard behavior, focus, and form behavior are contractual.
192
+ - Components must work inside structurally isolated Program iframes.
193
+ - Public types and JSDoc are part of the product.
194
+
195
+ ## Development
196
+
197
+ ```bash
198
+ bun install --frozen-lockfile
199
+ bun run verify
200
+ ```
201
+
202
+ `verify` type-checks the source and tests, runs the behavior suite, rebuilds the
203
+ package, packs the publication artifact, installs it into a temporary consumer,
204
+ and checks its runtime, TypeScript, and public subpath entry points.
205
+
206
+ `Button` is the only interactive component currently exported. Private
207
+ acceptance suites also compare Field, Select, Dialog, and Context Menu
208
+ candidates against the same implementation-independent behaviors; those
209
+ candidates are not part of the package's public surface. Field covers labeling,
210
+ descriptions, validation, native states, and value changes. Select covers
211
+ collections, keyboard input, disabled options, form submission, and cleanup.
212
+ Dialog covers modal semantics, focus, dismissal, nesting, state changes, and
213
+ cleanup. Context Menu covers invocation, focus, actions, disabled items,
214
+ dismissal, and cleanup.
@@ -0,0 +1,25 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import type { ButtonProps as AriaButtonProps } from "react-aria-components";
3
+ import { type ScaleLevel } from "@phreshos/core";
4
+ import { type RadiusProps } from "./radius.js";
5
+ type NativeButtonProps = Omit<AriaButtonProps, "children" | "className" | "isDisabled" | "isPending" | "onClick" | "onPress" | "style">;
6
+ /** Properties accepted by the shared interactive button. */
7
+ export interface ButtonProps extends NativeButtonProps, RadiusProps {
8
+ /** Visible Button content. */
9
+ readonly children?: ReactNode;
10
+ /** Native class name applied without replacing the component contract. */
11
+ readonly className?: string;
12
+ /** Prevents focus and activation. */
13
+ readonly disabled?: boolean;
14
+ /** Prevents activation while keeping the Button focusable. */
15
+ readonly pending?: boolean;
16
+ /** Runs once for a normalized pointer, Enter, or Space activation. */
17
+ readonly onPress?: () => void;
18
+ /** Derives the Button's spacing from the Theme's concrete default. */
19
+ readonly size?: ScaleLevel;
20
+ /** Additional native styles that do not replace the Button's identity. */
21
+ readonly style?: CSSProperties;
22
+ }
23
+ /** A Theme-aware action with normalized pointer and keyboard behavior. */
24
+ export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
25
+ export {};
package/dist/button.js ADDED
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { Button as AriaButton } from "react-aria-components";
4
+ import { scale } from "@phreshos/core";
5
+ import { resolveRadius } from "./radius.js";
6
+ import { useTheme } from "./theme-provider.js";
7
+ /** A Theme-aware action with normalized pointer and keyboard behavior. */
8
+ export const Button = forwardRef(function Button({ children, disabled = false, pending = false, onPress, radius = "medium", size = "medium", style, type = "button", ...properties }, ref) {
9
+ const theme = useTheme();
10
+ const spacing = scale(theme.spacing, size);
11
+ const borderRadius = resolveRadius(radius, theme);
12
+ return _jsx(AriaButton, { ...properties, ref: ref, type: type, isDisabled: disabled, isPending: pending, onPress: onPress, style: ({ isFocusVisible, isHovered, isPressed }) => buttonStyle({
13
+ borderRadius,
14
+ disabled,
15
+ isFocusVisible,
16
+ isHovered,
17
+ isPressed,
18
+ pending,
19
+ size,
20
+ spacing,
21
+ foreground: theme.foreground,
22
+ style
23
+ }), children: children });
24
+ });
25
+ function buttonStyle({ borderRadius, disabled, isFocusVisible, isHovered, isPressed, pending, size, spacing, foreground, style }) {
26
+ const fontSize = buttonFontSizes[size];
27
+ const height = Math.max(size === "xsmall" ? 24 : 28, 20 + spacing);
28
+ return {
29
+ ...style,
30
+ appearance: "none",
31
+ display: "inline-grid",
32
+ gridAutoFlow: "column",
33
+ gridAutoColumns: "max-content",
34
+ placeItems: "center",
35
+ flexShrink: 0,
36
+ minWidth: 0,
37
+ height,
38
+ paddingBlock: 0,
39
+ paddingInline: Math.max(8, spacing * 2 / 3),
40
+ gap: Math.max(4, spacing / 2),
41
+ border: "1px solid rgba(255, 255, 255, 0.45)",
42
+ borderRadius,
43
+ outline: "none",
44
+ color: foreground,
45
+ backgroundColor: `rgba(255, 255, 255, ${isPressed ? 0.42 : isHovered ? 0.5 : 0.3})`,
46
+ boxShadow: isFocusVisible
47
+ ? "0 0 0 2px rgba(255, 255, 255, 0.85), inset 0 1px 0 rgba(255, 255, 255, 0.8)"
48
+ : "inset 0 1px 0 rgba(255, 255, 255, 0.8)",
49
+ opacity: disabled ? 0.46 : pending ? 0.68 : 1,
50
+ transform: isPressed ? "scale(0.95)" : "scale(1)",
51
+ transition: "background-color 100ms ease, box-shadow 100ms ease, opacity 100ms ease, transform 100ms ease",
52
+ cursor: disabled ? "not-allowed" : pending ? "progress" : "pointer",
53
+ font: "inherit",
54
+ fontSize,
55
+ fontWeight: 650,
56
+ lineHeight: 1,
57
+ textAlign: "center",
58
+ textDecoration: "none",
59
+ userSelect: "none",
60
+ WebkitTapHighlightColor: "transparent"
61
+ };
62
+ }
63
+ const buttonFontSizes = Object.freeze({
64
+ xsmall: 10,
65
+ small: 11,
66
+ medium: 12,
67
+ large: 13,
68
+ xlarge: 14
69
+ });
@@ -0,0 +1,2 @@
1
+ /** Applies material opacity without restricting the source CSS color syntax. */
2
+ export declare function colorOpacity(value: string, opacity: number): string;
package/dist/color.js ADDED
@@ -0,0 +1,5 @@
1
+ /** Applies material opacity without restricting the source CSS color syntax. */
2
+ export function colorOpacity(value, opacity) {
3
+ const percentage = Math.round(opacity * 10_000) / 100;
4
+ return `color-mix(in srgb, ${value} ${percentage}%, transparent)`;
5
+ }
package/dist/flex.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { ComponentPropsWithoutRef } from "react";
2
+ import type { LayoutAlignment, LayoutGap, LayoutJustification } from "./layout.js";
3
+ /** Properties accepted by the Flex layout primitive. */
4
+ export interface FlexProps extends ComponentPropsWithoutRef<"div"> {
5
+ /** Main-axis direction. The browser default is `row`. */
6
+ readonly direction?: "row" | "row-reverse" | "column" | "column-reverse";
7
+ /** Cross-axis alignment of the children. */
8
+ readonly align?: LayoutAlignment;
9
+ /** Distribution of children along the main axis. */
10
+ readonly justify?: LayoutJustification;
11
+ /** Space between children. Numbers are pixels. */
12
+ readonly gap?: LayoutGap;
13
+ /** Enables wrapping, or reverses the wrapped cross-axis order. */
14
+ readonly wrap?: boolean | "reverse";
15
+ /** Uses `inline-flex` instead of `flex`. */
16
+ readonly inline?: boolean;
17
+ }
18
+ /** A predictable Flexbox container with no visual appearance of its own. */
19
+ export declare const Flex: import("react").ForwardRefExoticComponent<FlexProps & import("react").RefAttributes<HTMLDivElement>>;
package/dist/flex.js ADDED
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { alignment, justification, resolveGap } from "./layout.js";
4
+ import { useThemeIfAvailable } from "./theme-provider.js";
5
+ /** A predictable Flexbox container with no visual appearance of its own. */
6
+ export const Flex = forwardRef(function Flex({ align, direction, gap, inline = false, justify, style, wrap, ...properties }, ref) {
7
+ const theme = useThemeIfAvailable();
8
+ return _jsx("div", { ...properties, ref: ref, style: {
9
+ ...style,
10
+ display: inline ? "inline-flex" : "flex",
11
+ alignItems: alignment(align) ?? style?.alignItems,
12
+ flexDirection: direction ?? style?.flexDirection,
13
+ flexWrap: wrap === undefined ? style?.flexWrap : wrap === "reverse" ? "wrap-reverse" : wrap ? "wrap" : "nowrap",
14
+ gap: gap === undefined ? style?.gap : resolveGap(gap, theme),
15
+ justifyContent: justification(justify) ?? style?.justifyContent
16
+ } });
17
+ });
@@ -0,0 +1,34 @@
1
+ import type { ComponentPropsWithoutRef } from "react";
2
+ import { type Colorable, type ColorLevel, type ScaleLevel } from "@phreshos/core";
3
+ import { type RadiusProps } from "./radius.js";
4
+ /** Native properties accepted by the shared glass container. */
5
+ export type GlassSurfaceProps = ComponentPropsWithoutRef<"div"> & Colorable<ColorLevel> & RadiusProps & Readonly<{
6
+ /** Distortion derived from the Theme's concrete default. */
7
+ distortion?: ScaleLevel;
8
+ /** Blur derived from the Theme's concrete default. */
9
+ blur?: ScaleLevel;
10
+ /** Saturation derived from the Theme's concrete default. */
11
+ saturation?: ScaleLevel;
12
+ /** Brightness derived from the Theme's concrete default. */
13
+ brightness?: ScaleLevel;
14
+ /** Material opacity derived from the Theme's concrete default. */
15
+ opacity?: ScaleLevel;
16
+ }>;
17
+ /**
18
+ * Contains content within the shared refracted glass material.
19
+ *
20
+ * Layout, spacing, radius, and external elevation remain the responsibility of
21
+ * the containing interface. Only the material itself is owned here.
22
+ */
23
+ export declare const GlassSurface: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & Colorable<ColorLevel> & RadiusProps & Readonly<{
24
+ /** Distortion derived from the Theme's concrete default. */
25
+ distortion?: ScaleLevel;
26
+ /** Blur derived from the Theme's concrete default. */
27
+ blur?: ScaleLevel;
28
+ /** Saturation derived from the Theme's concrete default. */
29
+ saturation?: ScaleLevel;
30
+ /** Brightness derived from the Theme's concrete default. */
31
+ brightness?: ScaleLevel;
32
+ /** Material opacity derived from the Theme's concrete default. */
33
+ opacity?: ScaleLevel;
34
+ }> & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useId } from "react";
3
+ import { color as deriveColor, scale, scaleMultiplier, themeLimits } from "@phreshos/core";
4
+ import { colorOpacity } from "./color.js";
5
+ import { resolveRadius } from "./radius.js";
6
+ import { useTheme } from "./theme-provider.js";
7
+ const shell = "inset 1px 1px 0 rgba(255, 255, 255, 0.7), inset -1px -1px 0 rgba(255, 255, 255, 0.28)";
8
+ /**
9
+ * Contains content within the shared refracted glass material.
10
+ *
11
+ * Layout, spacing, radius, and external elevation remain the responsibility of
12
+ * the containing interface. Only the material itself is owned here.
13
+ */
14
+ export const GlassSurface = forwardRef(function GlassSurface({ blur = "medium", brightness = "medium", children, color = "base", distortion = "medium", opacity = "medium", radius, saturation = "medium", style, ...properties }, ref) {
15
+ const theme = useTheme();
16
+ const filter = `phresh-glass-${useId().replaceAll(":", "")}`;
17
+ const backdrop = glassBackdrop(theme.glass, { blur, brightness, saturation });
18
+ const alpha = boundedOpacity(scale(theme.glass.opacity, opacity));
19
+ const tint = deriveColor(theme.background)[color];
20
+ const borderRadius = resolveRadius(radius, theme);
21
+ return _jsxs("div", { ...properties, ref: ref, style: {
22
+ ...style,
23
+ ...(borderRadius === undefined ? {} : { borderRadius }),
24
+ color: theme.foreground,
25
+ backgroundColor: colorOpacity(tint, alpha),
26
+ backgroundImage: `linear-gradient(to bottom, ${colorOpacity(tint, boundedOpacity(alpha * 5 / 3))}, ${colorOpacity(tint, boundedOpacity(alpha / 2))})`,
27
+ boxShadow: shell,
28
+ backdropFilter: `url(#${filter}) ${backdrop}`,
29
+ WebkitBackdropFilter: backdrop,
30
+ isolation: "isolate",
31
+ overflow: "hidden"
32
+ }, children: [_jsx("svg", { "aria-hidden": "true", width: "0", height: "0", style: { position: "absolute" }, children: _jsxs("filter", { id: filter, x: "0%", y: "0%", width: "100%", height: "100%", children: [_jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "0.008 0.008", numOctaves: "2", seed: "92", result: "noise" }), _jsx("feGaussianBlur", { in: "noise", stdDeviation: "2", result: "blurred" }), _jsx("feDisplacementMap", { in: "SourceGraphic", in2: "blurred", scale: scale(theme.glass.distortion, distortion), xChannelSelector: "R", yChannelSelector: "G" })] }) }), children] });
33
+ });
34
+ function glassBackdrop(glass, levels) {
35
+ return `blur(${scale(glass.blur, levels.blur)}px) saturate(${scaleMultiplier(glass.saturation, levels.saturation)}) brightness(${scaleMultiplier(glass.brightness, levels.brightness)})`;
36
+ }
37
+ /** Keeps every derived tint within the system's maximum glass opacity. */
38
+ function boundedOpacity(value) {
39
+ return Math.round(Math.min(themeLimits.glass.opacity.maximum, Math.max(0, value)) * 10_000) / 10_000;
40
+ }
package/dist/grid.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { ComponentPropsWithoutRef, CSSProperties } from "react";
2
+ import type { LayoutAlignment, LayoutGap, LayoutJustification } from "./layout.js";
3
+ /** Properties accepted by the Grid layout primitive. */
4
+ export interface GridProps extends ComponentPropsWithoutRef<"div"> {
5
+ /** Equal-width column count or native CSS column-track expression. */
6
+ readonly columns?: number | string;
7
+ /** Equal-height row count or native CSS row-track expression. */
8
+ readonly rows?: number | string;
9
+ /** Automatic placement direction. */
10
+ readonly flow?: CSSProperties["gridAutoFlow"];
11
+ /** Cross-axis alignment of items inside their grid areas. */
12
+ readonly align?: LayoutAlignment;
13
+ /** Distribution of the grid tracks along the inline axis. */
14
+ readonly justify?: LayoutJustification;
15
+ /** Space between rows and columns. Numbers are pixels. */
16
+ readonly gap?: LayoutGap;
17
+ /** Uses `inline-grid` instead of `grid`. */
18
+ readonly inline?: boolean;
19
+ }
20
+ /** A predictable CSS Grid container with no visual appearance of its own. */
21
+ export declare const Grid: import("react").ForwardRefExoticComponent<GridProps & import("react").RefAttributes<HTMLDivElement>>;
package/dist/grid.js ADDED
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { alignment, justification, resolveGap, tracks } from "./layout.js";
4
+ import { useThemeIfAvailable } from "./theme-provider.js";
5
+ /** A predictable CSS Grid container with no visual appearance of its own. */
6
+ export const Grid = forwardRef(function Grid({ align, columns, flow, gap, inline = false, justify, rows, style, ...properties }, ref) {
7
+ const theme = useThemeIfAvailable();
8
+ return _jsx("div", { ...properties, ref: ref, style: {
9
+ ...style,
10
+ display: inline ? "inline-grid" : "grid",
11
+ alignItems: alignment(align) ?? style?.alignItems,
12
+ gap: gap === undefined ? style?.gap : resolveGap(gap, theme),
13
+ gridAutoFlow: flow ?? style?.gridAutoFlow,
14
+ gridTemplateColumns: tracks(columns, "columns") ?? style?.gridTemplateColumns,
15
+ gridTemplateRows: tracks(rows, "rows") ?? style?.gridTemplateRows,
16
+ justifyContent: justification(justify) ?? style?.justifyContent
17
+ } });
18
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Public entry point for icons that belong to the React UI visual language.
3
+ *
4
+ * Icons enter this surface only after their source, accessibility behavior,
5
+ * and customization contract have been established.
6
+ */
7
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Public entry point for icons that belong to the React UI visual language.
3
+ *
4
+ * Icons enter this surface only after their source, accessibility behavior,
5
+ * and customization contract have been established.
6
+ */
7
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { ThemeProperties } from "@phreshos/core";
3
+ import { type Spacing } from "./spacing.js";
4
+ /** Cross-axis alignment shared by layout containers. */
5
+ export type LayoutAlignment = "start" | "center" | "end" | "stretch" | "baseline";
6
+ /** Main-axis distribution shared by layout containers. */
7
+ export type LayoutJustification = "start" | "center" | "end" | "between" | "around" | "evenly";
8
+ /** A spacing value applied between layout children. */
9
+ export type LayoutGap = Spacing;
10
+ export declare function alignment(value: LayoutAlignment | undefined): CSSProperties["alignItems"] | undefined;
11
+ export declare function justification(value: LayoutJustification | undefined): CSSProperties["justifyContent"] | undefined;
12
+ /** Resolves semantic spacing while preserving explicit CSS gap values. */
13
+ export declare function resolveGap(value: LayoutGap | undefined, theme: ThemeProperties | null): CSSProperties["gap"];
14
+ export declare function tracks(value: number | string | undefined, property: "columns" | "rows"): string | undefined;
package/dist/layout.js ADDED
@@ -0,0 +1,34 @@
1
+ import { resolveSpacing } from "./spacing.js";
2
+ const alignments = {
3
+ start: "flex-start",
4
+ center: "center",
5
+ end: "flex-end",
6
+ stretch: "stretch",
7
+ baseline: "baseline"
8
+ };
9
+ const justifications = {
10
+ start: "flex-start",
11
+ center: "center",
12
+ end: "flex-end",
13
+ between: "space-between",
14
+ around: "space-around",
15
+ evenly: "space-evenly"
16
+ };
17
+ export function alignment(value) {
18
+ return value === undefined ? undefined : alignments[value];
19
+ }
20
+ export function justification(value) {
21
+ return value === undefined ? undefined : justifications[value];
22
+ }
23
+ /** Resolves semantic spacing while preserving explicit CSS gap values. */
24
+ export function resolveGap(value, theme) {
25
+ return resolveSpacing(value, theme);
26
+ }
27
+ export function tracks(value, property) {
28
+ if (value === undefined || typeof value === "string")
29
+ return value;
30
+ if (!Number.isInteger(value) || value < 1) {
31
+ throw new TypeError(`Grid ${property} must be a positive integer or CSS track expression`);
32
+ }
33
+ return `repeat(${value}, minmax(0, 1fr))`;
34
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Public entry point for the React UI library.
3
+ *
4
+ * Components enter this surface only after their implementation-independent
5
+ * behavior contract has been established by the package's tests.
6
+ */
7
+ export { ThemeProvider, useTheme, type ThemeProviderProps } from "./theme-provider.js";
8
+ export { Flex, type FlexProps } from "./flex.js";
9
+ export { Grid, type GridProps } from "./grid.js";
10
+ export { GlassSurface, type GlassSurfaceProps } from "./glass-surface.js";
11
+ export { Button, type ButtonProps } from "./button.js";
12
+ export type { LayoutAlignment, LayoutGap, LayoutJustification } from "./layout.js";
13
+ export { resolveRadius, type Radius, type RadiusProps } from "./radius.js";
14
+ export { resolveSpacing, type Spacing } from "./spacing.js";
package/dist/main.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Public entry point for the React UI library.
3
+ *
4
+ * Components enter this surface only after their implementation-independent
5
+ * behavior contract has been established by the package's tests.
6
+ */
7
+ export { ThemeProvider, useTheme } from "./theme-provider.js";
8
+ export { Flex } from "./flex.js";
9
+ export { Grid } from "./grid.js";
10
+ export { GlassSurface } from "./glass-surface.js";
11
+ export { Button } from "./button.js";
12
+ export { resolveRadius } from "./radius.js";
13
+ export { resolveSpacing } from "./spacing.js";
@@ -0,0 +1,10 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { Shapeable, ThemeProperties } from "@phreshos/core";
3
+ import { type ScaleLevel } from "@phreshos/core";
4
+ /** A Theme-derived level, pixel value, or explicit CSS corner radius. */
5
+ export type Radius = ScaleLevel | number | (string & {});
6
+ /** Shared semantic corner-radius capability for React UI components. */
7
+ export interface RadiusProps extends Shapeable<Radius> {
8
+ }
9
+ /** Resolves a Radius while preserving explicit CSS and pixel values. */
10
+ export declare function resolveRadius(value: Radius | undefined, theme: ThemeProperties | null): CSSProperties["borderRadius"];
package/dist/radius.js ADDED
@@ -0,0 +1,9 @@
1
+ import { isScaleLevel, scale } from "@phreshos/core";
2
+ /** Resolves a Radius while preserving explicit CSS and pixel values. */
3
+ export function resolveRadius(value, theme) {
4
+ if (!isScaleLevel(value))
5
+ return value;
6
+ if (!theme)
7
+ throw new Error("A semantic radius requires a ThemeProvider");
8
+ return scale(theme.radius, value);
9
+ }
@@ -0,0 +1,8 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { ThemeProperties } from "@phreshos/core";
3
+ import { type ScaleLevel } from "@phreshos/core";
4
+ /** A Theme-derived level, pixel value, or explicit CSS spacing value. */
5
+ export type Spacing = ScaleLevel | number | (string & {});
6
+ /** Resolves spacing while preserving explicit CSS and pixel values. */
7
+ export declare function resolveSpacing(value: ScaleLevel, theme: ThemeProperties | null): number;
8
+ export declare function resolveSpacing(value: Spacing | undefined, theme: ThemeProperties | null): CSSProperties["gap"];
@@ -0,0 +1,8 @@
1
+ import { isScaleLevel, scale } from "@phreshos/core";
2
+ export function resolveSpacing(value, theme) {
3
+ if (!isScaleLevel(value))
4
+ return value;
5
+ if (!theme)
6
+ throw new Error("Semantic spacing requires a ThemeProvider");
7
+ return scale(theme.spacing, value);
8
+ }
@@ -0,0 +1,29 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ThemeProperties } from "@phreshos/core";
3
+ /** Provides the nearest Theme value to one React subtree. */
4
+ export declare function ThemeProvider({ children, theme }: ThemeProviderProps): import("react").JSX.Element;
5
+ /** Returns the complete snapshot supplied by the nearest ThemeProvider. */
6
+ export declare function useTheme(): Readonly<{
7
+ background: string;
8
+ foreground: string;
9
+ accent: string;
10
+ spacing: number;
11
+ radius: number;
12
+ glass: import("@phreshos/core").ThemeGlass;
13
+ }>;
14
+ /** Internal optional read used by primitives with both raw and themed values. */
15
+ export declare function useThemeIfAvailable(): Readonly<{
16
+ background: string;
17
+ foreground: string;
18
+ accent: string;
19
+ spacing: number;
20
+ radius: number;
21
+ glass: import("@phreshos/core").ThemeGlass;
22
+ }> | null;
23
+ /** Properties accepted by ThemeProvider. */
24
+ export interface ThemeProviderProps {
25
+ /** Content that receives this Theme instead of any outer Theme. */
26
+ readonly children: ReactNode;
27
+ /** Complete Theme value for this subtree. */
28
+ readonly theme: ThemeProperties;
29
+ }
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from "react";
3
+ const missing = Symbol("ThemeProvider");
4
+ const ThemeContext = createContext(missing);
5
+ /** Provides the nearest Theme value to one React subtree. */
6
+ export function ThemeProvider({ children, theme }) {
7
+ return _jsx(ThemeContext.Provider, { value: theme, children: children });
8
+ }
9
+ /** Returns the complete snapshot supplied by the nearest ThemeProvider. */
10
+ export function useTheme() {
11
+ const properties = useContext(ThemeContext);
12
+ if (properties === missing)
13
+ throw new Error("useTheme() requires a ThemeProvider");
14
+ return properties;
15
+ }
16
+ /** Internal optional read used by primitives with both raw and themed values. */
17
+ export function useThemeIfAvailable() {
18
+ const properties = useContext(ThemeContext);
19
+ return properties === missing ? null : properties;
20
+ }
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@phreshos/react-ui",
3
+ "version": "0.1.0",
4
+ "description": "React components for coherent PhreshOS Program interfaces.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "dist/main.js",
8
+ "types": "dist/main.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/main.d.ts",
12
+ "default": "./dist/main.js"
13
+ },
14
+ "./icons": {
15
+ "types": "./dist/icons/main.d.ts",
16
+ "default": "./dist/icons/main.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "LICENSE",
22
+ "README.md"
23
+ ],
24
+ "author": "Zohayr SLILEH",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/PhreshOS/react-ui.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/PhreshOS/react-ui/issues"
32
+ },
33
+ "homepage": "https://github.com/PhreshOS/react-ui#readme",
34
+ "keywords": [
35
+ "phreshos",
36
+ "react",
37
+ "components",
38
+ "ui"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "provenance": true
43
+ },
44
+ "packageManager": "bun@1.3.14",
45
+ "scripts": {
46
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
47
+ "check": "tsc --noEmit && tsc -p tsconfig.test.json",
48
+ "test": "vitest run --environment jsdom",
49
+ "build": "node --run clean && tsc --noEmit false --outDir dist --rootDir source",
50
+ "verify:package": "node scripts/verify-package.mjs",
51
+ "verify": "node --run check && node --run test && node --run build && node --run verify:package",
52
+ "prepack": "node --run test && node --run build"
53
+ },
54
+ "peerDependencies": {
55
+ "@phreshos/core": "^0.1.1",
56
+ "react": "^19.2.0",
57
+ "react-dom": "^19.2.0"
58
+ },
59
+ "dependencies": {
60
+ "react-aria-components": "^1.20.0"
61
+ },
62
+ "devDependencies": {
63
+ "@base-ui/react": "^1.7.0",
64
+ "@phreshos/core": "^0.1.1",
65
+ "@testing-library/dom": "^10.4.1",
66
+ "@testing-library/react": "^16.3.0",
67
+ "@testing-library/user-event": "^14.6.1",
68
+ "@types/react": "^19.2.18",
69
+ "@types/react-dom": "^19.2.4",
70
+ "jsdom": "^30.0.1",
71
+ "react": "^19.2.8",
72
+ "react-dom": "^19.2.8",
73
+ "typescript": "^6.0.3",
74
+ "vitest": "^4.1.1"
75
+ }
76
+ }