@phreshos/react-ui 0.1.53 → 0.2.1
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/dist/app-layout.d.ts +12 -0
- package/dist/app-layout.js +141 -28
- package/dist/color-field.d.ts +3 -1
- package/dist/color-field.js +21 -4
- package/dist/dialog.d.ts +0 -6
- package/dist/dialog.js +10 -8
- package/dist/drawer.d.ts +2 -1
- package/dist/drawer.js +7 -3
- package/dist/foundation/color.d.ts +7 -0
- package/dist/foundation/color.js +25 -0
- package/dist/grid-list.d.ts +5 -0
- package/dist/grid-list.js +3 -2
- package/dist/main.d.ts +2 -2
- package/dist/main.js +1 -1
- package/dist/surface/surface.d.ts +8 -1
- package/dist/surface/surface.js +23 -6
- package/dist/table.d.ts +8 -1
- package/dist/table.js +33 -6
- package/dist/typography.d.ts +7 -0
- package/dist/typography.js +2 -1
- package/package.json +2 -2
package/dist/app-layout.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
|
|
2
2
|
import { type SurfaceDepth } from "./surface/surface.js";
|
|
3
|
+
/** At this width or less, a sidebar beside the content would crowd it out. */
|
|
4
|
+
export declare const narrowAppLayoutWidth = 640;
|
|
3
5
|
export interface AppLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
6
|
readonly children: ReactNode;
|
|
5
7
|
/** The sidebar's width; eighteen times the Appearance spacing by default. */
|
|
@@ -15,16 +17,26 @@ export type AppLayoutSidebarProps = AppLayoutRegionProps & Readonly<{
|
|
|
15
17
|
footer?: ReactNode;
|
|
16
18
|
}>;
|
|
17
19
|
export type AppLayoutTitleProps = HTMLAttributes<HTMLHeadingElement>;
|
|
20
|
+
/** In a narrow layout, the button that opens the sidebar's Drawer; otherwise nothing. */
|
|
21
|
+
declare function AppLayoutSidebarToggle(): import("react").JSX.Element | null;
|
|
22
|
+
/** Whether the nearest AppLayout is narrow, and closing its sidebar's Drawer, such as after a choice in it. */
|
|
23
|
+
export declare function useAppLayout(): {
|
|
24
|
+
narrow: boolean;
|
|
25
|
+
closeSidebar: () => void;
|
|
26
|
+
};
|
|
18
27
|
export declare const AppLayout: import("react").ForwardRefExoticComponent<AppLayoutProps & import("react").RefAttributes<HTMLDivElement>> & {
|
|
19
28
|
Title: import("react").ForwardRefExoticComponent<AppLayoutTitleProps & import("react").RefAttributes<HTMLHeadingElement>>;
|
|
20
29
|
Sidebar: import("react").ForwardRefExoticComponent<AppLayoutRegionProps & Readonly<{
|
|
21
30
|
/** What stays at the foot of the sidebar while the rest scrolls, such as work in progress. */
|
|
22
31
|
footer?: ReactNode;
|
|
23
32
|
}> & import("react").RefAttributes<HTMLElement>>;
|
|
33
|
+
SidebarToggle: typeof AppLayoutSidebarToggle;
|
|
24
34
|
Header: import("react").ForwardRefExoticComponent<AppLayoutRegionProps & import("react").RefAttributes<HTMLElement>>;
|
|
25
35
|
Content: import("react").ForwardRefExoticComponent<AppLayoutRegionProps & Readonly<{
|
|
26
36
|
/** How the content's Surface stands in the layout: recessed into it by default, or raised. */
|
|
27
37
|
depth?: SurfaceDepth;
|
|
28
38
|
}> & import("react").RefAttributes<HTMLElement>>;
|
|
39
|
+
Placeholder: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
|
|
29
40
|
Footer: import("react").ForwardRefExoticComponent<AppLayoutRegionProps & import("react").RefAttributes<HTMLElement>>;
|
|
30
41
|
};
|
|
42
|
+
export {};
|
package/dist/app-layout.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, forwardRef, useContext } from "react";
|
|
2
|
+
import { createContext, forwardRef, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useState } from "react";
|
|
3
|
+
import { PanelLeft } from "lucide-react";
|
|
4
|
+
import { Button } from "./button.js";
|
|
3
5
|
import { controlFontSizes, controlFontWeight } from "./control/control.js";
|
|
4
6
|
import { headerHeight } from "./foundation/layout.js";
|
|
5
7
|
import { resolveRadius } from "./foundation/radius.js";
|
|
@@ -8,39 +10,69 @@ import { useVisual } from "./foundation/visual.js";
|
|
|
8
10
|
import { ScrollArea } from "./scroll-area.js";
|
|
9
11
|
import { Surface } from "./surface/surface.js";
|
|
10
12
|
import { containerPadding } from "./foundation/spacing.js";
|
|
11
|
-
|
|
13
|
+
import { Drawer } from "./drawer.js";
|
|
14
|
+
/** At this width or less, a sidebar beside the content would crowd it out. */
|
|
15
|
+
export const narrowAppLayoutWidth = 640;
|
|
16
|
+
const AppLayoutContext = createContext(null);
|
|
17
|
+
// Kept apart from the state, so a part that names itself does not render again when it is read.
|
|
18
|
+
const AppLayoutRegistry = createContext(null);
|
|
12
19
|
/**
|
|
13
20
|
* The frame of a program's interface: a title above a sidebar, and a header,
|
|
14
21
|
* the content, and an optional footer beside them. Only the content is a
|
|
15
22
|
* Surface, the same inset region as a Panel's content; the other regions sit
|
|
16
23
|
* on whatever holds the layout, separated by spacing alone.
|
|
24
|
+
*
|
|
25
|
+
* At `narrowAppLayoutWidth` or less it gives the content the whole width: the title and sidebar
|
|
26
|
+
* wait in a Drawer, which `AppLayout.SidebarToggle` opens.
|
|
17
27
|
*/
|
|
18
28
|
const AppLayoutRoot = forwardRef(function AppLayout({ children, sidebarWidth, style, ...properties }, ref) {
|
|
19
29
|
const visual = useVisual();
|
|
20
30
|
const inset = scale(visual.spacing, "small");
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
const [element, setElement] = useState(null);
|
|
32
|
+
const narrow = useNarrow(element);
|
|
33
|
+
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
34
|
+
const [title, setTitle] = useState(null);
|
|
35
|
+
const [sidebarLabel, setSidebarLabel] = useState(undefined);
|
|
36
|
+
const attach = useCallback((value) => { setElement(value); assign(ref, value); }, [ref]);
|
|
37
|
+
// Widening puts the sidebar back beside the content, so its Drawer has nothing left to hold.
|
|
38
|
+
useEffect(() => { if (!narrow)
|
|
39
|
+
setSidebarOpen(false); }, [narrow]);
|
|
40
|
+
const state = useMemo(() => ({ narrow, sidebarOpen, setSidebarOpen, title, sidebarLabel }), [narrow, sidebarOpen, title, sidebarLabel]);
|
|
41
|
+
const registry = useMemo(() => ({ setTitle, setSidebarLabel }), []);
|
|
42
|
+
return _jsx(AppLayoutRegistry.Provider, { value: registry, children: _jsx(AppLayoutContext.Provider, { value: state, children: _jsx("div", { ...properties, ref: attach, style: {
|
|
43
|
+
position: "relative",
|
|
44
|
+
display: "grid",
|
|
45
|
+
gridTemplateColumns: narrow ? "minmax(0, 1fr)" : `${typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth ?? `${visual.spacing * 18}px`} minmax(0, 1fr)`,
|
|
46
|
+
gridTemplateRows: "auto minmax(0, 1fr) auto",
|
|
47
|
+
gridTemplateAreas: narrow ? `"header" "content" "footer"` : `"title header" "sidebar content" "sidebar footer"`,
|
|
48
|
+
columnGap: narrow ? 0 : inset,
|
|
49
|
+
// The header row supplies the space above the content, as in a Panel.
|
|
50
|
+
padding: inset,
|
|
51
|
+
paddingTop: 0,
|
|
52
|
+
boxSizing: "border-box",
|
|
53
|
+
width: "100%",
|
|
54
|
+
height: "100%",
|
|
55
|
+
minWidth: 0,
|
|
56
|
+
minHeight: 0,
|
|
57
|
+
// The frame of a program: its text takes the Appearance text color.
|
|
58
|
+
color: visual.colors.foreground,
|
|
59
|
+
fontFamily: "inherit",
|
|
60
|
+
...style
|
|
61
|
+
}, children: children }) }) });
|
|
40
62
|
});
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
const visuallyHidden = {
|
|
64
|
+
position: "absolute", width: 1, height: 1, margin: -1, padding: 0, border: 0,
|
|
65
|
+
overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap"
|
|
66
|
+
};
|
|
67
|
+
/** The program's title above the sidebar, in the header row; in a narrow layout, atop its Drawer. */
|
|
68
|
+
const AppLayoutTitle = forwardRef(function AppLayoutTitle({ style, children, ...properties }, ref) {
|
|
43
69
|
const { spacing } = useLayout();
|
|
70
|
+
const { narrow } = useLayoutState();
|
|
71
|
+
const { setTitle } = useRegistry();
|
|
72
|
+
useLayoutEffect(() => { setTitle(children); return () => setTitle(null); }, [children, setTitle]);
|
|
73
|
+
// Out of sight while narrow, but still the page's heading, so what is named by it keeps its name.
|
|
74
|
+
if (narrow)
|
|
75
|
+
return _jsx("h1", { ...properties, ref: ref, style: visuallyHidden, children: children });
|
|
44
76
|
return _jsx("h1", { ...properties, ref: ref, style: {
|
|
45
77
|
gridArea: "title",
|
|
46
78
|
display: "flex",
|
|
@@ -52,12 +84,22 @@ const AppLayoutTitle = forwardRef(function AppLayoutTitle({ style, ...properties
|
|
|
52
84
|
fontSize: controlFontSizes.large,
|
|
53
85
|
fontWeight: controlFontWeight,
|
|
54
86
|
...style
|
|
55
|
-
} });
|
|
87
|
+
}, children: children });
|
|
56
88
|
});
|
|
57
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* The sidebar, such as a program's navigation. It scrolls on its own, above its footer. In a
|
|
91
|
+
* narrow layout it waits in a Drawer under the title; choosing something in it is the program's to
|
|
92
|
+
* follow with `useAppLayout().closeSidebar()`.
|
|
93
|
+
*/
|
|
58
94
|
const AppLayoutSidebar = forwardRef(function AppLayoutSidebar({ children, footer, style, ...properties }, ref) {
|
|
59
95
|
const { spacing } = useLayout();
|
|
96
|
+
const { narrow, sidebarOpen, setSidebarOpen, title } = useLayoutState();
|
|
97
|
+
const { setSidebarLabel } = useRegistry();
|
|
60
98
|
const padding = scale(spacing, "small");
|
|
99
|
+
const label = properties["aria-label"];
|
|
100
|
+
useLayoutEffect(() => { setSidebarLabel(label); return () => setSidebarLabel(undefined); }, [label, setSidebarLabel]);
|
|
101
|
+
if (narrow)
|
|
102
|
+
return _jsxs(Drawer, { open: sidebarOpen, onClose: () => setSidebarOpen(false), title: title, "aria-label": label, children: [children, footer != null && _jsx("div", { style: { paddingTop: padding }, children: footer })] });
|
|
61
103
|
return _jsxs("aside", { ...properties, ref: ref, style: { gridArea: "sidebar", minHeight: 0, display: "flex", flexDirection: "column", ...style }, children: [_jsx(ScrollArea, { style: { flex: "1 1 auto", minHeight: 0 }, children: _jsx("div", { style: { padding }, children: children }) }), footer != null && _jsx("div", { style: { flex: "none", paddingInline: padding, paddingBottom: padding }, children: footer })] });
|
|
62
104
|
});
|
|
63
105
|
/** The header row above the content, as tall as a Window or Panel header. */
|
|
@@ -73,7 +115,10 @@ const AppLayoutHeader = forwardRef(function AppLayoutHeader({ style, ...properti
|
|
|
73
115
|
...style
|
|
74
116
|
} });
|
|
75
117
|
});
|
|
76
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* The content, on its Surface. It scrolls on its own, padded by the Appearance spacing. It is a size
|
|
120
|
+
* container, so what stands in for the content can fill what shows of it.
|
|
121
|
+
*/
|
|
77
122
|
const AppLayoutContent = forwardRef(function AppLayoutContent({ children, depth = "recessed", style, ...properties }, ref) {
|
|
78
123
|
const { spacing, radius } = useLayout();
|
|
79
124
|
return _jsx(Surface, { as: "main", ...properties, ref: ref, depth: depth, material: "extended", radius: resolveRadius("medium", radius), style: {
|
|
@@ -82,9 +127,26 @@ const AppLayoutContent = forwardRef(function AppLayoutContent({ children, depth
|
|
|
82
127
|
minHeight: 0,
|
|
83
128
|
// The Surface owns its shape: what scrolls inside stays within its corners.
|
|
84
129
|
overflow: "clip",
|
|
130
|
+
containerType: "size",
|
|
85
131
|
...style
|
|
86
132
|
}, children: _jsx(ScrollArea, { style: { height: "100%" }, children: _jsx("div", { style: { padding: containerPadding(spacing) }, children: children }) }) });
|
|
87
133
|
});
|
|
134
|
+
/**
|
|
135
|
+
* What stands in for the content while there is none to show: a wait, what went wrong, or that there
|
|
136
|
+
* is nothing. It stands in the middle of the content, wherever the content would have been.
|
|
137
|
+
*/
|
|
138
|
+
const AppLayoutPlaceholder = forwardRef(function AppLayoutPlaceholder({ style, ...properties }, ref) {
|
|
139
|
+
const { spacing } = useLayout();
|
|
140
|
+
return _jsx("div", { ...properties, ref: ref, style: {
|
|
141
|
+
display: "grid",
|
|
142
|
+
placeItems: "center",
|
|
143
|
+
alignContent: "center",
|
|
144
|
+
gap: scale(spacing, "medium"),
|
|
145
|
+
textAlign: "center",
|
|
146
|
+
minHeight: `calc(100cqh - ${containerPadding(spacing) * 2}px)`,
|
|
147
|
+
...style
|
|
148
|
+
} });
|
|
149
|
+
});
|
|
88
150
|
/** An optional row below the content, such as the actions that apply to it. */
|
|
89
151
|
const AppLayoutFooter = forwardRef(function AppLayoutFooter({ style, ...properties }, ref) {
|
|
90
152
|
const { spacing } = useLayout();
|
|
@@ -99,15 +161,66 @@ const AppLayoutFooter = forwardRef(function AppLayoutFooter({ style, ...properti
|
|
|
99
161
|
...style
|
|
100
162
|
} });
|
|
101
163
|
});
|
|
102
|
-
|
|
103
|
-
|
|
164
|
+
/** In a narrow layout, the button that opens the sidebar's Drawer; otherwise nothing. */
|
|
165
|
+
function AppLayoutSidebarToggle() {
|
|
166
|
+
const { narrow, sidebarOpen, setSidebarOpen, sidebarLabel } = useLayoutState();
|
|
167
|
+
if (!narrow)
|
|
168
|
+
return null;
|
|
169
|
+
return _jsx(Button, { iconOnly: true, depth: "flat", size: "small", "aria-label": sidebarLabel ?? "Sidebar", "aria-expanded": sidebarOpen, onPress: () => setSidebarOpen(!sidebarOpen), children: _jsx(PanelLeft, {}) });
|
|
170
|
+
}
|
|
171
|
+
/** Whether the nearest AppLayout is narrow, and closing its sidebar's Drawer, such as after a choice in it. */
|
|
172
|
+
export function useAppLayout() {
|
|
173
|
+
const { narrow, setSidebarOpen } = useLayoutState();
|
|
174
|
+
const closeSidebar = useCallback(() => setSidebarOpen(false), [setSidebarOpen]);
|
|
175
|
+
return useMemo(() => ({ narrow, closeSidebar }), [narrow, closeSidebar]);
|
|
176
|
+
}
|
|
177
|
+
function useLayoutState() {
|
|
178
|
+
const state = useContext(AppLayoutContext);
|
|
179
|
+
if (!state)
|
|
180
|
+
throw new Error("AppLayout parts must be used inside AppLayout");
|
|
181
|
+
return state;
|
|
182
|
+
}
|
|
183
|
+
function useRegistry() {
|
|
184
|
+
const registry = useContext(AppLayoutRegistry);
|
|
185
|
+
if (!registry)
|
|
104
186
|
throw new Error("AppLayout parts must be used inside AppLayout");
|
|
187
|
+
return registry;
|
|
188
|
+
}
|
|
189
|
+
function useLayout() {
|
|
190
|
+
useLayoutState();
|
|
105
191
|
return useVisual();
|
|
106
192
|
}
|
|
193
|
+
/** Follows whether the layout itself, not the window, is narrow. */
|
|
194
|
+
function useNarrow(element) {
|
|
195
|
+
const [narrow, setNarrow] = useState(false);
|
|
196
|
+
useLayoutEffect(() => {
|
|
197
|
+
if (!element)
|
|
198
|
+
return;
|
|
199
|
+
// A width of 0 is a layout not laid out yet, not a narrow one.
|
|
200
|
+
const follow = (width) => { if (width > 0)
|
|
201
|
+
setNarrow(width <= narrowAppLayoutWidth); };
|
|
202
|
+
follow(element.getBoundingClientRect().width);
|
|
203
|
+
if (typeof ResizeObserver !== "function")
|
|
204
|
+
return;
|
|
205
|
+
const observer = new ResizeObserver(([entry]) => { if (entry)
|
|
206
|
+
follow(entry.contentRect.width); });
|
|
207
|
+
observer.observe(element);
|
|
208
|
+
return () => observer.disconnect();
|
|
209
|
+
}, [element]);
|
|
210
|
+
return narrow;
|
|
211
|
+
}
|
|
212
|
+
function assign(ref, value) {
|
|
213
|
+
if (typeof ref === "function")
|
|
214
|
+
ref(value);
|
|
215
|
+
else if (ref)
|
|
216
|
+
ref.current = value;
|
|
217
|
+
}
|
|
107
218
|
export const AppLayout = Object.assign(AppLayoutRoot, {
|
|
108
219
|
Title: AppLayoutTitle,
|
|
109
220
|
Sidebar: AppLayoutSidebar,
|
|
221
|
+
SidebarToggle: AppLayoutSidebarToggle,
|
|
110
222
|
Header: AppLayoutHeader,
|
|
111
223
|
Content: AppLayoutContent,
|
|
224
|
+
Placeholder: AppLayoutPlaceholder,
|
|
112
225
|
Footer: AppLayoutFooter
|
|
113
226
|
});
|
package/dist/color-field.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ColorFieldProps extends Omit<AriaColorFieldProps, ControlOverri
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* A color typed as text: a hex value, or one channel when `channel` is set.
|
|
15
|
-
* Inside a ColorPicker it edits the picker's color.
|
|
15
|
+
* Inside a ColorPicker it edits the picker's color. A hex value applies as soon
|
|
16
|
+
* as it is a whole color, as the picker's other parts apply while they move,
|
|
17
|
+
* not only when the field is left.
|
|
16
18
|
*/
|
|
17
19
|
export declare const ColorField: import("react").ForwardRefExoticComponent<ColorFieldProps & import("react").RefAttributes<HTMLInputElement>>;
|
package/dist/color-field.js
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
|
-
import { ColorField as AriaColorField, Input as AriaInput } from "react-aria-components";
|
|
2
|
+
import { forwardRef, useContext } from "react";
|
|
3
|
+
import { ColorField as AriaColorField, ColorPickerStateContext, Input as AriaInput, parseColor } from "react-aria-components";
|
|
4
4
|
import { colorValue } from "./control/color-value.js";
|
|
5
5
|
import { useControlMetrics } from "./control/control.js";
|
|
6
6
|
import { FieldFeedback, FieldLabel, fieldStyle } from "./control/field.js";
|
|
7
7
|
import { nativeTextStyle, TextWell } from "./control/text-control.js";
|
|
8
8
|
import MotionStyle, { textControlClass } from "./foundation/motion-style.js";
|
|
9
9
|
import { dimmedClass } from "./surface/surface.js";
|
|
10
|
+
/**
|
|
11
|
+
* A whole six-digit hex color, with or without its `#`. A three-digit one is
|
|
12
|
+
* also the start of a six-digit one, so it waits until the field is left.
|
|
13
|
+
*/
|
|
14
|
+
const completeHex = /^#?[\da-f]{6}$/i;
|
|
10
15
|
/**
|
|
11
16
|
* A color typed as text: a hex value, or one channel when `channel` is set.
|
|
12
|
-
* Inside a ColorPicker it edits the picker's color.
|
|
17
|
+
* Inside a ColorPicker it edits the picker's color. A hex value applies as soon
|
|
18
|
+
* as it is a whole color, as the picker's other parts apply while they move,
|
|
19
|
+
* not only when the field is left.
|
|
13
20
|
*/
|
|
14
21
|
export const ColorField = forwardRef(function ColorField({ label, description, errorMessage, disabled, readOnly, required, invalid, value, defaultValue, onChange, size, color = "background", radius, style, className, placeholder, material, ...properties }, ref) {
|
|
15
22
|
const metrics = useControlMetrics(size, radius);
|
|
16
|
-
|
|
23
|
+
const picker = useContext(ColorPickerStateContext);
|
|
24
|
+
function typed(text) {
|
|
25
|
+
if (properties.channel !== undefined || !completeHex.test(text.trim()))
|
|
26
|
+
return;
|
|
27
|
+
const next = parseColor(text.trim().startsWith("#") ? text.trim() : `#${text.trim()}`);
|
|
28
|
+
if (picker)
|
|
29
|
+
picker.setColor(next);
|
|
30
|
+
else
|
|
31
|
+
onChange?.(colorValue(next));
|
|
32
|
+
}
|
|
33
|
+
return _jsxs(AriaColorField, { ...properties, ...(value !== undefined ? { value } : {}), ...(defaultValue !== undefined ? { defaultValue } : {}), ...(onChange !== undefined ? { onChange: (next) => onChange(next === null ? null : colorValue(next)) } : {}), className: dimmedClass(disabled ?? false, className), isDisabled: disabled, isReadOnly: readOnly, isRequired: required, isInvalid: invalid, style: fieldStyle(metrics, style), children: [_jsx(MotionStyle, {}), _jsx(FieldLabel, { label: label }), _jsx(AriaInput, { ref: ref, placeholder: placeholder, className: textControlClass, render: (native, state) => _jsx(TextWell, { color: color, material: material, metrics: metrics, state: state, children: _jsx("input", { ...native, onChange: event => { native.onChange?.(event); typed(event.currentTarget.value); } }) }), style: { ...nativeTextStyle(metrics, false), fontVariantNumeric: "tabular-nums" } }), _jsx(FieldFeedback, { metrics: metrics, description: description, errorMessage: errorMessage })] });
|
|
17
34
|
});
|
package/dist/dialog.d.ts
CHANGED
|
@@ -21,13 +21,7 @@ export interface DialogBackdropProps extends Omit<AriaModalOverlayProps, AriaOve
|
|
|
21
21
|
/** Whether Escape closes the Dialog. */
|
|
22
22
|
readonly keyboardDismissable?: boolean;
|
|
23
23
|
readonly style?: CSSProperties;
|
|
24
|
-
/**
|
|
25
|
-
* How the interface behind the Dialog is held back: `dim` darkens it, and
|
|
26
|
-
* `blur` blurs it with the Appearance material while darkening it lightly.
|
|
27
|
-
*/
|
|
28
|
-
readonly variant?: DialogBackdropVariant;
|
|
29
24
|
}
|
|
30
|
-
export type DialogBackdropVariant = "dim" | "blur";
|
|
31
25
|
export declare const DialogBackdrop: import("react").ForwardRefExoticComponent<DialogBackdropProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
32
26
|
export interface DialogContentProps extends Omit<AriaDialogProps, "children" | "className" | "style">, SurfaceOwnProps {
|
|
33
27
|
readonly children?: AriaDialogProps["children"];
|
package/dist/dialog.js
CHANGED
|
@@ -18,21 +18,25 @@ export function DialogRoot({ children, ...state }) {
|
|
|
18
18
|
export const DialogTrigger = forwardRef(function DialogTrigger(properties, ref) {
|
|
19
19
|
return _jsx(Button, { ...properties, ref: ref });
|
|
20
20
|
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
/** How far every backdrop darkens the interface behind a Dialog. */
|
|
22
|
+
const backdropDimming = 0.5;
|
|
23
|
+
/** The share of the Appearance material's blur a backdrop takes: lighter than a frosted Surface. */
|
|
24
|
+
const backdropBlurShare = 0.5;
|
|
25
|
+
export const DialogBackdrop = forwardRef(function DialogBackdrop({ children, className, dir, dismissable = false, keyboardDismissable = true, style, ...attributes }, ref) {
|
|
25
26
|
const visual = useVisual();
|
|
26
27
|
const inset = scale(visual.spacing, "medium");
|
|
27
28
|
const colors = visual.colors;
|
|
28
29
|
// The backdrop dims toward whichever Appearance color is darker, in any Theme.
|
|
29
30
|
const darker = darkCanvas(colors) ? colors.background : colors.foreground;
|
|
30
|
-
const backdrop = colorOpacity(darker, backdropDimming
|
|
31
|
-
|
|
31
|
+
const backdrop = colorOpacity(darker, backdropDimming);
|
|
32
|
+
// Where the material blurs, the backdrop blurs too, more lightly; without it, it only dims.
|
|
33
|
+
const blur = visual.material.backdrop > 0 ? `blur(${visual.material.backdrop * backdropBlurShare}px)` : undefined;
|
|
32
34
|
const transition = overlayTransition(visual);
|
|
33
35
|
const direction = resolveDirection(dir, useDirection());
|
|
34
36
|
return _jsxs(_Fragment, { children: [_jsx(MotionStyle, {}), _jsx(AriaModalOverlay, { ...attributes, isDismissable: dismissable, isKeyboardDismissDisabled: !keyboardDismissable, ref: ref, dir: direction, className: [backdropMotionClass, className].filter(Boolean).join(" "), style: {
|
|
35
37
|
...transition,
|
|
38
|
+
backdropFilter: blur,
|
|
39
|
+
WebkitBackdropFilter: blur,
|
|
36
40
|
position: "fixed",
|
|
37
41
|
inset: 0,
|
|
38
42
|
zIndex: overlayLayer,
|
|
@@ -42,8 +46,6 @@ export const DialogBackdrop = forwardRef(function DialogBackdrop({ children, cla
|
|
|
42
46
|
padding: inset,
|
|
43
47
|
overflow: "auto",
|
|
44
48
|
background: backdrop,
|
|
45
|
-
backdropFilter: blur,
|
|
46
|
-
WebkitBackdropFilter: blur,
|
|
47
49
|
...style
|
|
48
50
|
}, children: children })] });
|
|
49
51
|
});
|
package/dist/drawer.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface DrawerProps {
|
|
|
16
16
|
* outside or Escape closes it. It slides in and back out along the same path, timed by the
|
|
17
17
|
* motion of a change in place, the way out its own animation so its end is seen; without motion it
|
|
18
18
|
* simply shows and goes. Only its position moves:
|
|
19
|
-
* fading it would switch off the blur it draws of what is behind it.
|
|
19
|
+
* fading it would switch off the blur it draws of what is behind it. The surface itself never
|
|
20
|
+
* scrolls, so its material stays behind all of it; what it holds scrolls inside it.
|
|
20
21
|
*/
|
|
21
22
|
export declare function Drawer({ open, onClose, title, className, style, children, ...properties }: DrawerProps): import("react").JSX.Element | null;
|
package/dist/drawer.js
CHANGED
|
@@ -4,6 +4,7 @@ import { controlFontSizes, controlFontWeight } from "./control/control.js";
|
|
|
4
4
|
import MotionStyle, { overlayTransition } from "./foundation/motion-style.js";
|
|
5
5
|
import { scale } from "./foundation/scale.js";
|
|
6
6
|
import { useVisual } from "./foundation/visual.js";
|
|
7
|
+
import { ScrollArea } from "./scroll-area.js";
|
|
7
8
|
import { Surface } from "./surface/surface.js";
|
|
8
9
|
/**
|
|
9
10
|
* A narrow layout's sidebar, over its content from the start side, such as the navigation a narrow
|
|
@@ -11,7 +12,8 @@ import { Surface } from "./surface/surface.js";
|
|
|
11
12
|
* outside or Escape closes it. It slides in and back out along the same path, timed by the
|
|
12
13
|
* motion of a change in place, the way out its own animation so its end is seen; without motion it
|
|
13
14
|
* simply shows and goes. Only its position moves:
|
|
14
|
-
* fading it would switch off the blur it draws of what is behind it.
|
|
15
|
+
* fading it would switch off the blur it draws of what is behind it. The surface itself never
|
|
16
|
+
* scrolls, so its material stays behind all of it; what it holds scrolls inside it.
|
|
15
17
|
*/
|
|
16
18
|
export function Drawer({ open, onClose, title, className, style, children, ...properties }) {
|
|
17
19
|
const visual = useVisual();
|
|
@@ -47,7 +49,9 @@ export function Drawer({ open, onClose, title, className, style, children, ...pr
|
|
|
47
49
|
boxSizing: "border-box",
|
|
48
50
|
width: `min(${visual.spacing * 20}px, calc(100% - ${visual.spacing * 4}px))`,
|
|
49
51
|
padding: inset,
|
|
50
|
-
|
|
52
|
+
display: "flex",
|
|
53
|
+
flexDirection: "column",
|
|
54
|
+
overflow: "hidden",
|
|
51
55
|
...style
|
|
52
|
-
}, children: [title != null && _jsx("div", { style: { padding: `${inset}px ${inset}px ${visual.spacing}px`, fontSize: controlFontSizes.xlarge, fontWeight: controlFontWeight }, children: title }), children] })] });
|
|
56
|
+
}, children: [title != null && _jsx("div", { style: { flex: "none", padding: `${inset}px ${inset}px ${visual.spacing}px`, fontSize: controlFontSizes.xlarge, fontWeight: controlFontWeight }, children: title }), _jsx(ScrollArea, { style: { flex: "1 1 auto", minHeight: 0 }, children: children })] })] });
|
|
53
57
|
}
|
|
@@ -54,6 +54,13 @@ export declare function canvasDarkness(colors: AppearanceColors): number;
|
|
|
54
54
|
* Depth therefore reads the same on a light, mid-tone, or dark canvas.
|
|
55
55
|
*/
|
|
56
56
|
export declare function recessColor(base: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* A paint moved just far enough from the paint it sits on to be told apart: at least `minimum`
|
|
59
|
+
* perceived difference (OKLab), stepping lighter, or darker only when lighter has no room. Raised
|
|
60
|
+
* and level things stand out the same way everywhere, whatever happens to be beneath them. A paint
|
|
61
|
+
* already far enough stays as it is.
|
|
62
|
+
*/
|
|
63
|
+
export declare function separateColor(paint: string, surroundings: string, minimum: number): string;
|
|
57
64
|
/** The lighter Appearance content candidate: the paint of anything lit from above, such as a thumb. */
|
|
58
65
|
export declare function lightColor(colors: AppearanceColors): string;
|
|
59
66
|
export declare function isColorLevel(value: string): value is ColorLevel;
|
package/dist/foundation/color.js
CHANGED
|
@@ -252,6 +252,31 @@ export function recessColor(base) {
|
|
|
252
252
|
}
|
|
253
253
|
/** The constant difference between a recess and the paint it sinks into. */
|
|
254
254
|
const recessContrast = 1.15;
|
|
255
|
+
const separateCache = new Map();
|
|
256
|
+
/**
|
|
257
|
+
* A paint moved just far enough from the paint it sits on to be told apart: at least `minimum`
|
|
258
|
+
* perceived difference (OKLab), stepping lighter, or darker only when lighter has no room. Raised
|
|
259
|
+
* and level things stand out the same way everywhere, whatever happens to be beneath them. A paint
|
|
260
|
+
* already far enough stays as it is.
|
|
261
|
+
*/
|
|
262
|
+
export function separateColor(paint, surroundings, minimum) {
|
|
263
|
+
const key = `${paint}|${surroundings}|${minimum}`;
|
|
264
|
+
const cached = separateCache.get(key);
|
|
265
|
+
if (cached !== undefined)
|
|
266
|
+
return cached;
|
|
267
|
+
const distance = perceivedDistance(paint, surroundings);
|
|
268
|
+
if (distance === null || distance >= minimum)
|
|
269
|
+
return remember(separateCache, key, paint);
|
|
270
|
+
const directions = ["#ffffff", "#000000"];
|
|
271
|
+
for (const toward of directions) {
|
|
272
|
+
for (let amount = 0.01; amount <= 0.6; amount += 0.01) {
|
|
273
|
+
const result = mixColor(paint, toward, Math.round(amount * 1_000) / 1_000);
|
|
274
|
+
if ((perceivedDistance(result, surroundings) ?? 0) >= minimum)
|
|
275
|
+
return remember(separateCache, key, result);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return remember(separateCache, key, paint);
|
|
279
|
+
}
|
|
255
280
|
/** The lighter Appearance content candidate: the paint of anything lit from above, such as a thumb. */
|
|
256
281
|
export function lightColor(colors) {
|
|
257
282
|
return darkCanvas(colors) ? colors.foreground : colors.default;
|
package/dist/grid-list.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ type GridListRootBaseProps<T extends object> = Omit<AriaGridListProps<T>, "class
|
|
|
12
12
|
restColor?: Color;
|
|
13
13
|
/** The narrowest an Item may be; the grid fits as many columns as that allows. Sixteen times the spacing by default. */
|
|
14
14
|
itemWidth?: CSSProperties["width"];
|
|
15
|
+
/**
|
|
16
|
+
* Whether fewer Items than fit share the whole width. Off by default, so an open collection keeps
|
|
17
|
+
* its Item width whatever it holds; on for a fixed set, such as a row of counts.
|
|
18
|
+
*/
|
|
19
|
+
stretch?: boolean;
|
|
15
20
|
size?: ScaleLevel;
|
|
16
21
|
style?: CSSProperties;
|
|
17
22
|
}>;
|
package/dist/grid-list.js
CHANGED
|
@@ -10,11 +10,12 @@ import { resolveDirection, useDirection } from "./foundation/direction.js";
|
|
|
10
10
|
import { collectionHeaderStyle } from "./list-box.js";
|
|
11
11
|
const GridListStyleContext = createContext(null);
|
|
12
12
|
const GridListRootImplementation = forwardRef(function GridList(properties, ref) {
|
|
13
|
-
const { className, color = "primary", restColor = "default", defaultValue: _defaultValue, itemWidth, onChange: _onChange, radius, selectionMode: _selectionMode, size, style, value: _value, ...native } = properties;
|
|
13
|
+
const { className, color = "primary", restColor = "default", defaultValue: _defaultValue, itemWidth, onChange: _onChange, radius, selectionMode: _selectionMode, size, stretch = false, style, value: _value, ...native } = properties;
|
|
14
14
|
const metrics = useControlMetrics(size, radius);
|
|
15
15
|
const width = itemWidth ?? metrics.visual.spacing * 16;
|
|
16
16
|
// As many columns as fit, each at least the Item width; a Section repeats the same columns.
|
|
17
|
-
|
|
17
|
+
// Stretched, the columns no Item fills fold away and the rest share their width.
|
|
18
|
+
const columns = `repeat(${stretch ? "auto-fit" : "auto-fill"}, minmax(min(${typeof width === "number" ? `${width}px` : width}, 100%), 1fr))`;
|
|
18
19
|
const context = useMemo(() => ({ color, restColor, metrics, columns }), [color, restColor, metrics, columns]);
|
|
19
20
|
const direction = resolveDirection(native.dir, useDirection());
|
|
20
21
|
return _jsx(GridListStyleContext.Provider, { value: context, children: _jsx(AriaDirectionBoundary, { direction: direction, children: _jsx(AriaGridList, { ...native, ...ariaSelection(properties, "single"), ref: ref, dir: direction, layout: "grid", className: className, style: state => ({
|
package/dist/main.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export { Loading, type LoadingProps } from "./loading.js";
|
|
|
62
62
|
export { FileTrigger, type FileTriggerProps } from "./file-trigger.js";
|
|
63
63
|
export { DropZone, type DropZoneProps } from "./drop-zone.js";
|
|
64
64
|
export { Fieldset, type FieldsetProps } from "./fieldset.js";
|
|
65
|
-
export { AppLayout, type AppLayoutContentProps, type AppLayoutProps, type AppLayoutRegionProps, type AppLayoutSidebarProps, type AppLayoutTitleProps } from "./app-layout.js";
|
|
65
|
+
export { AppLayout, narrowAppLayoutWidth, useAppLayout, type AppLayoutContentProps, type AppLayoutProps, type AppLayoutRegionProps, type AppLayoutSidebarProps, type AppLayoutTitleProps } from "./app-layout.js";
|
|
66
66
|
export { ColorSwatch, type ColorSwatchProps } from "./color-swatch.js";
|
|
67
67
|
export { ColorSwatchPicker, type ColorSwatchPickerProps, type ColorSwatchPickerItemProps } from "./color-swatch-picker.js";
|
|
68
68
|
export { ColorSlider, type ColorSliderProps } from "./color-slider.js";
|
|
@@ -77,7 +77,7 @@ export { Popover, type PopoverProps, type PopoverTriggerProps, type PopoverConte
|
|
|
77
77
|
export { Menu, type MenuProps, type MenuNoSelectionProps, type MenuSingleSelectionProps, type MenuMultipleSelectionProps, type MenuMultipleValue, type MenuItemProps, type MenuSectionProps, type MenuHeaderProps, type MenuSeparatorProps, type MenuSubmenuProps, type MenuSubmenuContentProps } from "./menu.js";
|
|
78
78
|
export { DropdownMenu, type DropdownMenuProps, type DropdownMenuTriggerProps } from "./dropdown-menu.js";
|
|
79
79
|
export { ContextMenu, type ContextMenuProps, type ContextMenuTriggerProps, type ContextMenuContentProps } from "./context-menu.js";
|
|
80
|
-
export { Dialog, type DialogProps, type DialogTriggerProps, type DialogBackdropProps, type
|
|
80
|
+
export { Dialog, type DialogProps, type DialogTriggerProps, type DialogBackdropProps, type DialogContentProps, type DialogHeaderProps, type DialogTitleProps, type DialogDescriptionProps, type DialogBodyProps, type DialogFooterProps, type DialogCloseProps } from "./dialog.js";
|
|
81
81
|
export { AlertDialog, type AlertDialogProps, type AlertDialogBackdropProps, type AlertDialogContentProps } from "./alert-dialog.js";
|
|
82
82
|
export { Tooltip, type TooltipProps, type TooltipTriggerProps, type TooltipContentProps } from "./tooltip.js";
|
|
83
83
|
export type { LayoutAlignment, LayoutGap, LayoutJustification } from "./foundation/layout.js";
|
package/dist/main.js
CHANGED
|
@@ -61,7 +61,7 @@ export { Loading } from "./loading.js";
|
|
|
61
61
|
export { FileTrigger } from "./file-trigger.js";
|
|
62
62
|
export { DropZone } from "./drop-zone.js";
|
|
63
63
|
export { Fieldset } from "./fieldset.js";
|
|
64
|
-
export { AppLayout } from "./app-layout.js";
|
|
64
|
+
export { AppLayout, narrowAppLayoutWidth, useAppLayout } from "./app-layout.js";
|
|
65
65
|
export { ColorSwatch } from "./color-swatch.js";
|
|
66
66
|
export { ColorSwatchPicker } from "./color-swatch-picker.js";
|
|
67
67
|
export { ColorSlider } from "./color-slider.js";
|
|
@@ -47,6 +47,11 @@ export interface SurfaceInteraction {
|
|
|
47
47
|
/** Marks a value-holding Surface whose current value is rejected. */
|
|
48
48
|
readonly invalid?: boolean;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* How far a nested Surface keeps from the one it sits on: farther on a dark canvas, where
|
|
52
|
+
* differences read smaller.
|
|
53
|
+
*/
|
|
54
|
+
export declare function nestingDistance(darkness: number): number;
|
|
50
55
|
/**
|
|
51
56
|
* The single visual primitive. Components express what they are through
|
|
52
57
|
* color, depth, and interaction; paint, grain, frost, edge, shadow, focus,
|
|
@@ -81,8 +86,10 @@ export declare function surfaceColor(color: Color, depth: SurfaceDepth, colors:
|
|
|
81
86
|
/** The color a Surface of this color and depth is painted with at rest, in the active Appearance. */
|
|
82
87
|
export declare function useSurfaceColor(color?: Color, depth?: SurfaceDepth): string;
|
|
83
88
|
/** Resolves every visual value of one Surface. Pure over its inputs. */
|
|
84
|
-
export declare function surfacePaint(visual: Visual, color: Color, depth: SurfaceDepth, options: MaterialMode | MaterialOptions | undefined, shadowOptions: ShadowOverrides["shadow"], interaction: SurfaceInteraction | undefined): {
|
|
89
|
+
export declare function surfacePaint(visual: Visual, color: Color, depth: SurfaceDepth, options: MaterialMode | MaterialOptions | undefined, shadowOptions: ShadowOverrides["shadow"], interaction: SurfaceInteraction | undefined, surroundings?: string | null): {
|
|
85
90
|
fill: string;
|
|
91
|
+
/** The paint at rest, or `null` for a clear Surface. */
|
|
92
|
+
rest: string | null;
|
|
86
93
|
text: string;
|
|
87
94
|
ring: string;
|
|
88
95
|
shadow: string;
|
package/dist/surface/surface.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { createElement, forwardRef, useId, useInsertionEffect } from "react";
|
|
3
|
-
import { canvasDarkness, colorOpacity, contrast, mixColor, readableColor, recessColor, resolveColor } from "../foundation/color.js";
|
|
2
|
+
import { createContext, createElement, forwardRef, useContext, useId, useInsertionEffect } from "react";
|
|
3
|
+
import { canvasDarkness, colorOpacity, contrast, mixColor, readableColor, recessColor, resolveColor, separateColor } from "../foundation/color.js";
|
|
4
4
|
import { resolveRadius } from "../foundation/radius.js";
|
|
5
5
|
import { useVisual } from "../foundation/visual.js";
|
|
6
6
|
import { grainSeed, grainSize, grainTexture } from "./grain.js";
|
|
7
7
|
import { flushPaintRules, paintClass, usePaintClasses } from "./paint-class.js";
|
|
8
8
|
import { resolveMaterialOptions } from "./material-options.js";
|
|
9
9
|
import { resolveShadowOptions, shadowStyle } from "./shadow-options.js";
|
|
10
|
+
/** The resting paint of the nearest painted Surface around, which a nested Surface keeps apart from. */
|
|
11
|
+
const SurroundingPaint = createContext(null);
|
|
12
|
+
/**
|
|
13
|
+
* How far a nested Surface keeps from the one it sits on: farther on a dark canvas, where
|
|
14
|
+
* differences read smaller.
|
|
15
|
+
*/
|
|
16
|
+
export function nestingDistance(darkness) {
|
|
17
|
+
return 0.04 + 0.03 * darkness;
|
|
18
|
+
}
|
|
10
19
|
/**
|
|
11
20
|
* The single visual primitive. Components express what they are through
|
|
12
21
|
* color, depth, and interaction; paint, grain, frost, edge, shadow, focus,
|
|
@@ -15,7 +24,8 @@ import { resolveShadowOptions, shadowStyle } from "./shadow-options.js";
|
|
|
15
24
|
export const SurfaceView = forwardRef(function Surface({ as: Element = "div", color = "background", depth = "raised", material, shadow, radius, interaction, className, children, style, ...properties }, ref) {
|
|
16
25
|
const visual = useVisual();
|
|
17
26
|
const identity = useId();
|
|
18
|
-
const
|
|
27
|
+
const surroundings = useContext(SurroundingPaint);
|
|
28
|
+
const paint = surfacePaint(visual, color, depth, material, shadow, interaction, surroundings);
|
|
19
29
|
const borderRadius = radius === undefined && style?.borderRadius !== undefined
|
|
20
30
|
? style.borderRadius
|
|
21
31
|
: resolveRadius(radius ?? "medium", visual.radius);
|
|
@@ -38,7 +48,9 @@ export const SurfaceView = forwardRef(function Surface({ as: Element = "div", co
|
|
|
38
48
|
className: [surfaceClass, paint.grain?.name, painted, dimmedClass(interaction?.disabled ?? false), className].filter(Boolean).join(" "),
|
|
39
49
|
// Without a document (server rendering), the same declarations travel inline.
|
|
40
50
|
style: painted === undefined ? { ...inlineDeclarations(declarations), ...style } : style
|
|
41
|
-
}, _jsx(SurfaceStyle, {}), paint.grain && _jsx(GrainStyle, { grain: paint.grain }), paint.distortion > 0 && _jsx(Refraction, { identity: identity, distortion: paint.distortion }),
|
|
51
|
+
}, _jsx(SurfaceStyle, {}), paint.grain && _jsx(GrainStyle, { grain: paint.grain }), paint.distortion > 0 && _jsx(Refraction, { identity: identity, distortion: paint.distortion }),
|
|
52
|
+
// A clear Surface paints nothing, so what is inside it sits on what is around it.
|
|
53
|
+
paint.rest === null ? children : _jsx(SurroundingPaint.Provider, { value: paint.rest, children: children }));
|
|
42
54
|
});
|
|
43
55
|
function inlineDeclarations(declarations) {
|
|
44
56
|
const result = {};
|
|
@@ -85,7 +97,7 @@ export function useSurfaceColor(color = "background", depth = "raised") {
|
|
|
85
97
|
return surfaceColor(color, depth, colors);
|
|
86
98
|
}
|
|
87
99
|
/** Resolves every visual value of one Surface. Pure over its inputs. */
|
|
88
|
-
export function surfacePaint(visual, color, depth, options, shadowOptions, interaction) {
|
|
100
|
+
export function surfacePaint(visual, color, depth, options, shadowOptions, interaction, surroundings = null) {
|
|
89
101
|
const { colors } = visual;
|
|
90
102
|
const base = resolveColor(color, colors);
|
|
91
103
|
const active = interaction !== undefined && !interaction.disabled;
|
|
@@ -95,12 +107,15 @@ export function surfacePaint(visual, color, depth, options, shadowOptions, inter
|
|
|
95
107
|
const darkness = canvasDarkness(colors);
|
|
96
108
|
// A recess moves away from its surroundings toward whichever extreme has
|
|
97
109
|
// room, by one constant perceptual difference on any canvas.
|
|
98
|
-
const
|
|
110
|
+
const own = depth === "recessed" ? surfaceColor(color, depth, colors) : base;
|
|
99
111
|
// A clear Surface, with no depth or a transparent color, has no substance of
|
|
100
112
|
// its own: it paints nothing at rest, takes the content color of whatever it
|
|
101
113
|
// sits on, and only reveals interaction as a veil. With no depth, a color
|
|
102
114
|
// other than the neutral one colors its content instead of a fill.
|
|
103
115
|
const clear = depth === "none" || base === "transparent";
|
|
116
|
+
// A raised or level Surface nested in another keeps apart from it, however deep the nesting goes.
|
|
117
|
+
// A recess keeps its own constant depth below its color, so a well reads the same wherever it is.
|
|
118
|
+
const rest = clear || depth === "recessed" || surroundings === null ? own : separateColor(own, surroundings, nestingDistance(darkness));
|
|
104
119
|
const tinted = depth === "none" && base !== "transparent" && base !== colors.default;
|
|
105
120
|
const fill = clear
|
|
106
121
|
? shift > 0 ? colorOpacity(colors.foreground, shift * clearVeil) : "transparent"
|
|
@@ -149,6 +164,8 @@ export function surfacePaint(visual, color, depth, options, shadowOptions, inter
|
|
|
149
164
|
: shadowStyle(resolveShadowOptions(typeof shadowOptions === "object" ? shadowOptions : {}, shadow));
|
|
150
165
|
return {
|
|
151
166
|
fill,
|
|
167
|
+
/** The paint at rest, or `null` for a clear Surface. */
|
|
168
|
+
rest: clear ? null : rest,
|
|
152
169
|
// The label is chosen once for the resting paint: interaction shades stay
|
|
153
170
|
// close to it, and re-deciding per state would flip text on near-ties.
|
|
154
171
|
text: tinted ? base : clear ? "inherit" : readableColor(rest, colors),
|
package/dist/table.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface TableHeaderProps<T extends object = object> extends Omit<AriaTa
|
|
|
32
32
|
readonly style?: CSSProperties;
|
|
33
33
|
}
|
|
34
34
|
export type TableHeaderComponent = <T extends object = object>(properties: TableHeaderProps<T> & RefAttributes<HTMLTableSectionElement | HTMLDivElement>) => ReactElement | null;
|
|
35
|
-
export interface TableColumnProps extends Omit<AriaColumnProps, "allowsSorting" | "allowsArrowNavigation" | "className" | "id" | "isRowHeader" | "style"> {
|
|
35
|
+
export interface TableColumnProps extends Omit<AriaColumnProps, "allowsSorting" | "allowsArrowNavigation" | "className" | "id" | "isRowHeader" | "style" | "width" | "minWidth" | "maxWidth" | "defaultWidth"> {
|
|
36
36
|
/** Whether arrow keys move between cells while focus is inside this one. */
|
|
37
37
|
readonly arrowNavigation?: boolean;
|
|
38
38
|
/** Identity of this Column, reported in sort changes. */
|
|
@@ -41,6 +41,13 @@ export interface TableColumnProps extends Omit<AriaColumnProps, "allowsSorting"
|
|
|
41
41
|
readonly rowHeader?: boolean;
|
|
42
42
|
/** Whether pressing this Column requests a sort. */
|
|
43
43
|
readonly sortable?: boolean;
|
|
44
|
+
/** The Column's width, in pixels or any CSS length. */
|
|
45
|
+
readonly width?: number | string;
|
|
46
|
+
/**
|
|
47
|
+
* For a Column without a width, which takes the room the others leave: the least it keeps when
|
|
48
|
+
* that room runs out.
|
|
49
|
+
*/
|
|
50
|
+
readonly minWidth?: number | string;
|
|
44
51
|
readonly className?: string;
|
|
45
52
|
readonly style?: CSSProperties;
|
|
46
53
|
}
|
package/dist/table.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, forwardRef, useContext, useMemo } from "react";
|
|
2
|
+
import { createContext, forwardRef, useCallback, useContext, useLayoutEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Cell as AriaCell, Column as AriaColumn, Row as AriaRow, Table as AriaTable, TableBody as AriaTableBody, TableHeader as AriaTableHeader } from "react-aria-components";
|
|
4
4
|
import { controlFontWeight, controlOpacity, transition, useControlMetrics, secondaryFontSize } from "./control/control.js";
|
|
5
5
|
import { separatorColor } from "./control/field.js";
|
|
@@ -16,11 +16,25 @@ const TableStyleContext = createContext(null);
|
|
|
16
16
|
const TableRoot = forwardRef(function Table(properties, ref) {
|
|
17
17
|
const { className, color = "primary", defaultValue: _defaultValue, onAction, onChange: _onChange, onSortChange, radius, selectOnPressUp, selectionMode = "none", size, sort, style, value: _value, ...native } = properties;
|
|
18
18
|
const metrics = useControlMetrics(size, radius);
|
|
19
|
-
|
|
19
|
+
// The least room each Column takes, from its width or its minimum.
|
|
20
|
+
const [least, setLeast] = useState({});
|
|
21
|
+
const measure = useCallback((column, room) => setLeast(current => {
|
|
22
|
+
if ((current[column] ?? null) === room)
|
|
23
|
+
return current;
|
|
24
|
+
const { [column]: _previous, ...rest } = current;
|
|
25
|
+
return room === null ? rest : { ...rest, [column]: room };
|
|
26
|
+
}), []);
|
|
27
|
+
const context = useMemo(() => ({ color, interactive: selectionMode !== "none" || onAction != null, metrics, measure }), [color, metrics, onAction, selectionMode, measure]);
|
|
28
|
+
const rooms = Object.values(least);
|
|
20
29
|
const direction = resolveDirection(native.dir, useDirection());
|
|
21
30
|
return _jsx(TableStyleContext.Provider, { value: context, children: _jsx(AriaDirectionBoundary, { direction: direction, children: _jsx(AriaTable, { ...native, shouldSelectOnPressUp: selectOnPressUp, ...ariaSelection(properties, "none"), ...(sort === undefined ? {} : { sortDescriptor: sort }), ref: ref, dir: direction, className: className, onRowAction: onAction == null ? undefined : key => onAction(stringKey(key)), onSortChange: onSortChange == null ? undefined : descriptor => onSortChange(tableSort(descriptor)), style: state => ({
|
|
22
31
|
width: "100%",
|
|
23
|
-
|
|
32
|
+
// Columns that state their room lay the Table out by it, and the Table never grows narrower
|
|
33
|
+
// than all of it together: past what holds it, it is scrolled, never crushed. Otherwise the
|
|
34
|
+
// Table is as wide as its content.
|
|
35
|
+
...rooms.length
|
|
36
|
+
? { tableLayout: "fixed", minWidth: `calc(${rooms.join(" + ")})` }
|
|
37
|
+
: { minWidth: "max-content" },
|
|
24
38
|
borderCollapse: "separate",
|
|
25
39
|
borderSpacing: 0,
|
|
26
40
|
boxSizing: "border-box",
|
|
@@ -40,9 +54,15 @@ const TableHeaderImplementation = forwardRef(function TableHeader({ className, s
|
|
|
40
54
|
useTableStyle();
|
|
41
55
|
return _jsx(AriaTableHeader, { ...properties, ref: ref, className: className, style: style });
|
|
42
56
|
});
|
|
43
|
-
const TableColumn = forwardRef(function TableColumn({ sortable = false, rowHeader, arrowNavigation, children, className, style, textValue, ...properties }, ref) {
|
|
44
|
-
const { metrics } = useTableStyle();
|
|
57
|
+
const TableColumn = forwardRef(function TableColumn({ sortable = false, rowHeader, arrowNavigation, children, className, style, textValue, width, minWidth, ...properties }, ref) {
|
|
58
|
+
const { metrics, measure } = useTableStyle();
|
|
45
59
|
const { colors } = metrics.visual;
|
|
60
|
+
const room = width ?? minWidth;
|
|
61
|
+
const least = room === undefined ? null : typeof room === "number" ? `${room}px` : room;
|
|
62
|
+
useLayoutEffect(() => {
|
|
63
|
+
measure(properties.id, least);
|
|
64
|
+
return () => measure(properties.id, null);
|
|
65
|
+
}, [measure, properties.id, least]);
|
|
46
66
|
return _jsx(AriaColumn, { ...properties, allowsArrowNavigation: arrowNavigation, ref: ref, isRowHeader: rowHeader, allowsSorting: sortable, className: className, textValue: textValue ?? (typeof children === "string" ? children : undefined), style: state => ({
|
|
47
67
|
...transition(metrics.visual, "color, outline-color"),
|
|
48
68
|
height: metrics.height,
|
|
@@ -56,8 +76,9 @@ const TableColumn = forwardRef(function TableColumn({ sortable = false, rowHeade
|
|
|
56
76
|
textAlign: "start",
|
|
57
77
|
userSelect: "none",
|
|
58
78
|
opacity: state.isHovered && sortable ? 1 : controlOpacity.secondary,
|
|
79
|
+
width,
|
|
59
80
|
...style
|
|
60
|
-
}), children: state => _jsxs("span", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: metrics.gap, minWidth: 0 }, children: [_jsx("span", { style: { minWidth: 0 }, children: typeof children === "function" ? children(state) : children }), sortable && state.sortDirection != null && _jsx(ChevronUp, { ...iconProps(14), style: { ...transition(metrics.visual, "rotate"), flexShrink: 0, rotate: state.sortDirection === "descending" ? "180deg" : "0deg" } })] }) });
|
|
81
|
+
}), children: state => _jsxs("span", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: metrics.gap, minWidth: 0 }, children: [_jsx("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: typeof children === "function" ? children(state) : children }), sortable && state.sortDirection != null && _jsx(ChevronUp, { ...iconProps(14), style: { ...transition(metrics.visual, "rotate"), flexShrink: 0, rotate: state.sortDirection === "descending" ? "180deg" : "0deg" } })] }) });
|
|
61
82
|
});
|
|
62
83
|
const TableBodyImplementation = forwardRef(function TableBody({ className, style, ...properties }, ref) {
|
|
63
84
|
return _jsx(AriaTableBody, { ...properties, ref: ref, className: className, style: style });
|
|
@@ -93,6 +114,12 @@ const TableCell = forwardRef(function TableCell({ arrowNavigation, className, st
|
|
|
93
114
|
outline: `3px solid ${state.isFocusVisible ? colorOpacity(metrics.visual.colors.primary, 0.34) : "transparent"}`,
|
|
94
115
|
outlineOffset: -3,
|
|
95
116
|
textAlign: "start",
|
|
117
|
+
// A narrow column cuts a cell's text short instead of letting it spill into the next. What
|
|
118
|
+
// the cell holds still paints a focus ring's width past its edge.
|
|
119
|
+
overflow: "clip",
|
|
120
|
+
overflowClipMargin: 3,
|
|
121
|
+
textOverflow: "ellipsis",
|
|
122
|
+
whiteSpace: "nowrap",
|
|
96
123
|
...style
|
|
97
124
|
}) });
|
|
98
125
|
});
|
package/dist/typography.d.ts
CHANGED
|
@@ -19,6 +19,13 @@ export interface TextProps {
|
|
|
19
19
|
readonly tone?: "default" | "secondary";
|
|
20
20
|
/** The control text scale; omitted, the text keeps the size around it. */
|
|
21
21
|
readonly size?: ScaleLevel;
|
|
22
|
+
/**
|
|
23
|
+
* Keeps the text to one line, cut short with an ellipsis where its space ends: for a name beside
|
|
24
|
+
* other things, such as an icon or a badge, in a row too narrow for all of it.
|
|
25
|
+
*/
|
|
26
|
+
readonly truncate?: boolean;
|
|
27
|
+
/** What a pointer resting on it shows, such as the whole of a text cut short. */
|
|
28
|
+
readonly title?: string;
|
|
22
29
|
/** A slot name when the text describes a field or item, such as `description`. */
|
|
23
30
|
readonly slot?: string;
|
|
24
31
|
readonly elementType?: string;
|
package/dist/typography.js
CHANGED
|
@@ -25,11 +25,12 @@ export const Heading = forwardRef(function Heading({ level = 2, size, style, ...
|
|
|
25
25
|
} });
|
|
26
26
|
});
|
|
27
27
|
/** Running text in the tone and scale of the interface. */
|
|
28
|
-
export const Text = forwardRef(function Text({ tone = "default", size, style, ...properties }, ref) {
|
|
28
|
+
export const Text = forwardRef(function Text({ tone = "default", size, truncate = false, style, ...properties }, ref) {
|
|
29
29
|
return _jsx(AriaText, { ...properties, ref: ref, style: {
|
|
30
30
|
fontSize: size === undefined ? undefined : controlFontSizes[size],
|
|
31
31
|
lineHeight: controlLineHeight,
|
|
32
32
|
opacity: tone === "secondary" ? controlOpacity.secondary : undefined,
|
|
33
|
+
...truncate && { display: "block", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
33
34
|
...style
|
|
34
35
|
} });
|
|
35
36
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/react-ui",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "React components for coherent PhreshOS Program interfaces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@base-ui/react": "^1.7.0",
|
|
59
59
|
"@internationalized/date": "^3.12.3",
|
|
60
|
-
"@phreshos/core": "^0.
|
|
60
|
+
"@phreshos/core": "^0.2.0",
|
|
61
61
|
"colorjs.io": "^0.7.1",
|
|
62
62
|
"lucide-react": "^1.48.0",
|
|
63
63
|
"react-aria": "^3.51.0",
|