@phreshos/react-ui 0.1.36 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +147 -167
  2. package/dist/accordion.d.ts +7 -6
  3. package/dist/accordion.js +5 -5
  4. package/dist/alert-dialog.d.ts +2 -2
  5. package/dist/alert-dialog.js +3 -1
  6. package/dist/alert.d.ts +17 -0
  7. package/dist/alert.js +30 -0
  8. package/dist/app-layout.d.ts +13 -0
  9. package/dist/app-layout.js +84 -0
  10. package/dist/button.d.ts +31 -27
  11. package/dist/button.js +48 -40
  12. package/dist/calendar-layout.d.ts +9 -5
  13. package/dist/calendar-layout.js +67 -78
  14. package/dist/calendar-trigger.d.ts +4 -4
  15. package/dist/calendar-trigger.js +7 -29
  16. package/dist/calendar.d.ts +2 -2
  17. package/dist/calendar.js +6 -5
  18. package/dist/checkbox.d.ts +4 -4
  19. package/dist/checkbox.js +16 -6
  20. package/dist/color-area.d.ts +20 -0
  21. package/dist/color-area.js +30 -0
  22. package/dist/color-field.d.ts +17 -0
  23. package/dist/color-field.js +17 -0
  24. package/dist/color-picker.d.ts +24 -0
  25. package/dist/color-picker.js +30 -0
  26. package/dist/color-slider.d.ts +19 -0
  27. package/dist/color-slider.js +29 -0
  28. package/dist/color-swatch-picker.d.ts +23 -0
  29. package/dist/color-swatch-picker.js +36 -0
  30. package/dist/color-swatch.d.ts +18 -0
  31. package/dist/color-swatch.js +20 -0
  32. package/dist/combo-box.d.ts +18 -10
  33. package/dist/combo-box.js +43 -58
  34. package/dist/context-menu.d.ts +14 -10
  35. package/dist/context-menu.js +7 -6
  36. package/dist/control/color-thumb.d.ts +13 -0
  37. package/dist/control/color-thumb.js +33 -0
  38. package/dist/control/color-value.d.ts +6 -0
  39. package/dist/control/color-value.js +7 -0
  40. package/dist/control/control.d.ts +60 -0
  41. package/dist/control/control.js +71 -0
  42. package/dist/control/field-button.d.ts +13 -0
  43. package/dist/control/field-button.js +28 -0
  44. package/dist/control/field.d.ts +20 -0
  45. package/dist/control/field.js +34 -0
  46. package/dist/control/icon.d.ts +11 -0
  47. package/dist/control/icon.js +8 -0
  48. package/dist/control/item.d.ts +37 -0
  49. package/dist/control/item.js +57 -0
  50. package/dist/control/open-state.d.ts +25 -0
  51. package/dist/control/open-state.js +8 -0
  52. package/dist/control/selection-track.d.ts +39 -0
  53. package/dist/control/selection-track.js +122 -0
  54. package/dist/control/selection.d.ts +44 -0
  55. package/dist/{selection.js → control/selection.js} +16 -0
  56. package/dist/control/surface-render.d.ts +10 -0
  57. package/dist/control/surface-render.js +12 -0
  58. package/dist/control/text-control.d.ts +32 -0
  59. package/dist/control/text-control.js +31 -0
  60. package/dist/control/toggle.d.ts +32 -0
  61. package/dist/control/toggle.js +80 -0
  62. package/dist/date-control.d.ts +9 -9
  63. package/dist/date-control.js +31 -34
  64. package/dist/date-field.d.ts +7 -6
  65. package/dist/date-field.js +8 -6
  66. package/dist/date-picker.d.ts +13 -6
  67. package/dist/date-picker.js +9 -6
  68. package/dist/date-range-picker.d.ts +15 -6
  69. package/dist/date-range-picker.js +12 -9
  70. package/dist/dialog.d.ts +29 -15
  71. package/dist/dialog.js +37 -28
  72. package/dist/disclosure.d.ts +8 -7
  73. package/dist/disclosure.js +31 -52
  74. package/dist/drop-zone.d.ts +18 -0
  75. package/dist/drop-zone.js +32 -0
  76. package/dist/dropdown-menu.d.ts +11 -8
  77. package/dist/dropdown-menu.js +5 -4
  78. package/dist/fieldset.d.ts +13 -0
  79. package/dist/fieldset.js +26 -0
  80. package/dist/file-trigger.d.ts +13 -0
  81. package/dist/file-trigger.js +6 -0
  82. package/dist/flex.d.ts +1 -1
  83. package/dist/flex.js +5 -4
  84. package/dist/foundation/appearance.d.ts +83 -0
  85. package/dist/foundation/appearance.js +38 -0
  86. package/dist/{aria-direction.js → foundation/aria-direction.js} +3 -1
  87. package/dist/foundation/color.d.ts +61 -0
  88. package/dist/foundation/color.js +247 -0
  89. package/dist/{layout.d.ts → foundation/layout.d.ts} +6 -4
  90. package/dist/{layout.js → foundation/layout.js} +7 -5
  91. package/dist/foundation/motion-style.d.ts +9 -0
  92. package/dist/foundation/motion-style.js +50 -0
  93. package/dist/foundation/preferences.d.ts +15 -0
  94. package/dist/{ui-provider.d.ts → foundation/provider.d.ts} +9 -5
  95. package/dist/{ui-provider.js → foundation/provider.js} +18 -27
  96. package/dist/{radius.d.ts → foundation/radius.d.ts} +6 -3
  97. package/dist/foundation/radius.js +11 -0
  98. package/dist/foundation/spacing.d.ts +11 -0
  99. package/dist/foundation/spacing.js +4 -0
  100. package/dist/foundation/visual.d.ts +42 -0
  101. package/dist/{appearance-context.js → foundation/visual.js} +41 -27
  102. package/dist/grid.d.ts +1 -1
  103. package/dist/grid.js +5 -4
  104. package/dist/icons/main.d.ts +5 -3
  105. package/dist/icons/main.js +5 -3
  106. package/dist/input.d.ts +2 -2
  107. package/dist/input.js +9 -7
  108. package/dist/list-box.d.ts +24 -19
  109. package/dist/list-box.js +52 -109
  110. package/dist/main.d.ts +28 -17
  111. package/dist/main.js +22 -9
  112. package/dist/menu.d.ts +28 -32
  113. package/dist/menu.js +47 -93
  114. package/dist/number-field.d.ts +17 -0
  115. package/dist/number-field.js +28 -0
  116. package/dist/panel.d.ts +10 -5
  117. package/dist/panel.js +33 -12
  118. package/dist/popover.d.ts +26 -12
  119. package/dist/popover.js +22 -15
  120. package/dist/progress-bar.d.ts +8 -10
  121. package/dist/progress-bar.js +18 -34
  122. package/dist/radio.d.ts +15 -12
  123. package/dist/radio.js +27 -37
  124. package/dist/range-calendar.d.ts +5 -3
  125. package/dist/range-calendar.js +8 -7
  126. package/dist/scroll-area.d.ts +2 -2
  127. package/dist/scroll-area.js +10 -9
  128. package/dist/segmented-control.d.ts +23 -0
  129. package/dist/segmented-control.js +38 -0
  130. package/dist/select.d.ts +40 -13
  131. package/dist/select.js +63 -18
  132. package/dist/slider.d.ts +7 -3
  133. package/dist/slider.js +51 -50
  134. package/dist/spinner.d.ts +5 -8
  135. package/dist/spinner.js +12 -27
  136. package/dist/surface/grain.d.ts +12 -0
  137. package/dist/surface/grain.js +65 -0
  138. package/dist/{material-options.d.ts → surface/material-options.d.ts} +1 -2
  139. package/dist/{material-options.js → surface/material-options.js} +2 -2
  140. package/dist/surface/paint-class.d.ts +13 -0
  141. package/dist/surface/paint-class.js +47 -0
  142. package/dist/{shadow-options.d.ts → surface/shadow-options.d.ts} +13 -1
  143. package/dist/{shadow-options.js → surface/shadow-options.js} +17 -2
  144. package/dist/surface/surface.d.ts +80 -0
  145. package/dist/surface/surface.js +239 -0
  146. package/dist/switch.d.ts +4 -5
  147. package/dist/switch.js +17 -8
  148. package/dist/table.d.ts +29 -32
  149. package/dist/table.js +68 -106
  150. package/dist/tabs.d.ts +16 -15
  151. package/dist/tabs.js +44 -86
  152. package/dist/textarea.d.ts +2 -2
  153. package/dist/textarea.js +10 -14
  154. package/dist/time-field.d.ts +6 -6
  155. package/dist/time-field.js +8 -6
  156. package/dist/toolbar.d.ts +6 -4
  157. package/dist/toolbar.js +16 -15
  158. package/dist/tooltip.d.ts +17 -8
  159. package/dist/tooltip.js +17 -15
  160. package/dist/tree.d.ts +22 -27
  161. package/dist/tree.js +54 -99
  162. package/dist/window.d.ts +4 -4
  163. package/dist/window.js +18 -20
  164. package/package.json +3 -3
  165. package/dist/appearance-context.d.ts +0 -54
  166. package/dist/appearance.d.ts +0 -11
  167. package/dist/appearance.js +0 -11
  168. package/dist/color.d.ts +0 -69
  169. package/dist/color.js +0 -192
  170. package/dist/control-surface.d.ts +0 -20
  171. package/dist/control-surface.js +0 -11
  172. package/dist/control.d.ts +0 -70
  173. package/dist/control.js +0 -105
  174. package/dist/field-style.d.ts +0 -3
  175. package/dist/field-style.js +0 -12
  176. package/dist/material-paint.d.ts +0 -13
  177. package/dist/material-paint.js +0 -119
  178. package/dist/motion-style.d.ts +0 -13
  179. package/dist/motion-style.js +0 -75
  180. package/dist/preferences.d.ts +0 -12
  181. package/dist/radius.js +0 -11
  182. package/dist/readiness.d.ts +0 -37
  183. package/dist/readiness.js +0 -134
  184. package/dist/selection.d.ts +0 -18
  185. package/dist/spacing.d.ts +0 -8
  186. package/dist/spacing.js +0 -8
  187. package/dist/surface-edge.d.ts +0 -11
  188. package/dist/surface-edge.js +0 -55
  189. package/dist/surface.d.ts +0 -27
  190. package/dist/surface.js +0 -92
  191. package/dist/text-control.d.ts +0 -10
  192. package/dist/text-control.js +0 -0
  193. package/dist/toggle-indicator.d.ts +0 -18
  194. package/dist/toggle-indicator.js +0 -49
  195. /package/dist/{aria-direction.d.ts → foundation/aria-direction.d.ts} +0 -0
  196. /package/dist/{direction.d.ts → foundation/direction.d.ts} +0 -0
  197. /package/dist/{direction.js → foundation/direction.js} +0 -0
  198. /package/dist/{overlay-placement.d.ts → foundation/overlay-placement.d.ts} +0 -0
  199. /package/dist/{overlay-placement.js → foundation/overlay-placement.js} +0 -0
  200. /package/dist/{preferences.js → foundation/preferences.js} +0 -0
  201. /package/dist/{scale.d.ts → foundation/scale.d.ts} +0 -0
  202. /package/dist/{scale.js → foundation/scale.js} +0 -0
@@ -0,0 +1,60 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import type { Color } from "../foundation/color.js";
3
+ import { type Radius } from "../foundation/radius.js";
4
+ import { type ScaleLevel } from "../foundation/scale.js";
5
+ import { type Visual } from "../foundation/visual.js";
6
+ /** Properties shared by every component that owns a paint. */
7
+ export interface ControlProps {
8
+ /** Base paint. Every interaction state is derived from it. */
9
+ readonly color?: Color;
10
+ /** Scale of the whole control, relative to Appearance spacing and inherited text. */
11
+ readonly size?: ScaleLevel;
12
+ readonly className?: string;
13
+ readonly style?: CSSProperties;
14
+ /** Prevents focus and activation. */
15
+ readonly disabled?: boolean;
16
+ }
17
+ /** Properties shared by every component that holds a value. */
18
+ export interface FieldProps {
19
+ /** Visible accessible label. Otherwise supply aria-label or aria-labelledby. */
20
+ readonly label?: ReactNode;
21
+ readonly description?: ReactNode;
22
+ readonly errorMessage?: ReactNode;
23
+ readonly required?: boolean;
24
+ readonly invalid?: boolean;
25
+ }
26
+ /** Names owned by our contract, never exposed again under their React Aria spelling. */
27
+ export type ControlOverrides = keyof ControlProps | keyof FieldProps | "children" | "isDisabled" | "isRequired" | "isInvalid" | "isReadOnly";
28
+ /** Relative text scale for control content. Only a component's root applies it. */
29
+ export declare const controlFontSizes: Readonly<Record<ScaleLevel, string>>;
30
+ export declare const controlFontWeight = 600;
31
+ /** Attenuation shared by every control family. */
32
+ export declare const controlOpacity: Readonly<{
33
+ pending: 0.68;
34
+ secondary: 0.66;
35
+ placeholder: 0.55;
36
+ }>;
37
+ /** Geometry of one control size, derived from Appearance spacing and radius. */
38
+ export interface ControlMetrics {
39
+ readonly visual: Visual;
40
+ readonly spacing: number;
41
+ readonly height: number;
42
+ readonly inset: number;
43
+ readonly gap: number;
44
+ readonly indicator: number;
45
+ /** Space between a collection's edge and its Items. */
46
+ readonly listInset: number;
47
+ readonly radius: CSSProperties["borderRadius"];
48
+ readonly fontSize: string;
49
+ }
50
+ /** Returns the shared metrics for one size and radius in the active Appearance. */
51
+ export declare function useControlMetrics(size?: ScaleLevel, radius?: Radius): ControlMetrics;
52
+ export declare function controlMetrics(visual: Visual, size: ScaleLevel, radius: Radius): ControlMetrics;
53
+ /**
54
+ * An Appearance radius level scaled to one control height, so a control keeps
55
+ * the same shape at every size: the medium control height carries the
56
+ * Appearance radius itself.
57
+ */
58
+ export declare function proportionalRadius(visual: Visual, height: number, level?: ScaleLevel): number;
59
+ /** A CSS transition for the given properties using the Appearance transaction. */
60
+ export declare function transition(visual: Visual, properties: string, factor?: number): CSSProperties;
@@ -0,0 +1,71 @@
1
+ import { resolveRadius } from "../foundation/radius.js";
2
+ import { isScaleLevel } from "../foundation/scale.js";
3
+ import { scale } from "../foundation/scale.js";
4
+ import { useVisual } from "../foundation/visual.js";
5
+ /** Relative text scale for control content. Only a component's root applies it. */
6
+ export const controlFontSizes = Object.freeze({
7
+ xsmall: "0.6875em",
8
+ small: "0.75em",
9
+ medium: "0.8125em",
10
+ large: "0.875em",
11
+ xlarge: "0.9375em"
12
+ });
13
+ export const controlFontWeight = 600;
14
+ /** Attenuation shared by every control family. */
15
+ export const controlOpacity = Object.freeze({
16
+ pending: 0.68,
17
+ secondary: 0.66,
18
+ placeholder: 0.55
19
+ });
20
+ const metrics = new WeakMap();
21
+ /** Returns the shared metrics for one size and radius in the active Appearance. */
22
+ export function useControlMetrics(size = "medium", radius = "medium") {
23
+ return controlMetrics(useVisual(), size, radius);
24
+ }
25
+ export function controlMetrics(visual, size, radius) {
26
+ let cache = metrics.get(visual);
27
+ if (cache === undefined)
28
+ metrics.set(visual, cache = new Map());
29
+ const key = `${size}:${radius}`;
30
+ const cached = cache.get(key);
31
+ if (cached !== undefined)
32
+ return cached;
33
+ const spacing = scale(visual.spacing, size);
34
+ const height = controlHeight(spacing);
35
+ const result = Object.freeze({
36
+ visual,
37
+ spacing,
38
+ height,
39
+ // Side padding follows the size, never below half the Appearance spacing,
40
+ // so a compact icon-only control stays close to square.
41
+ inset: Math.max(visual.spacing / 2, spacing),
42
+ gap: Math.max(4, spacing / 2),
43
+ indicator: Math.round(14 + spacing / 3),
44
+ listInset: Math.max(4, spacing / 3),
45
+ radius: isScaleLevel(radius) ? proportionalRadius(visual, height, radius) : resolveRadius(radius, visual.radius),
46
+ fontSize: controlFontSizes[size]
47
+ });
48
+ cache.set(key, result);
49
+ return result;
50
+ }
51
+ /** The height of a control whose size level resolves to this spacing. */
52
+ function controlHeight(spacing) {
53
+ return Math.round(22 + spacing);
54
+ }
55
+ /**
56
+ * An Appearance radius level scaled to one control height, so a control keeps
57
+ * the same shape at every size: the medium control height carries the
58
+ * Appearance radius itself.
59
+ */
60
+ export function proportionalRadius(visual, height, level = "medium") {
61
+ const medium = controlHeight(visual.spacing);
62
+ return Math.round(scale(visual.radius, level) * height / medium * 100) / 100;
63
+ }
64
+ /** A CSS transition for the given properties using the Appearance transaction. */
65
+ export function transition(visual, properties, factor = 1) {
66
+ return {
67
+ transitionProperty: properties,
68
+ transitionDuration: `${visual.duration * factor}ms`,
69
+ transitionTimingFunction: visual.easing
70
+ };
71
+ }
@@ -0,0 +1,13 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import { type ButtonProps as AriaButtonProps } from "react-aria-components";
3
+ import type { ControlMetrics } from "./control.js";
4
+ /**
5
+ * A quiet button held at the end of a field's well, such as a date picker's
6
+ * calendar or a number field's steppers. It veils like any quiet control and
7
+ * sits the same distance from every edge of the well.
8
+ */
9
+ export declare function FieldButton({ metrics, children, style, ...properties }: Readonly<Omit<AriaButtonProps, "children" | "render" | "style"> & {
10
+ metrics: ControlMetrics;
11
+ children: ReactNode;
12
+ style?: CSSProperties;
13
+ }>): import("react").JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Button as AriaButton } from "react-aria-components";
3
+ import { surfaceRender } from "./surface-render.js";
4
+ /**
5
+ * A quiet button held at the end of a field's well, such as a date picker's
6
+ * calendar or a number field's steppers. It veils like any quiet control and
7
+ * sits the same distance from every edge of the well.
8
+ */
9
+ export function FieldButton({ metrics, children, style, ...properties }) {
10
+ const size = metrics.height - 8;
11
+ return _jsx(AriaButton, { ...properties, render: surfaceRender("button", state => ({
12
+ color: "transparent",
13
+ radius: metrics.radius,
14
+ interaction: { hovered: state.isHovered, pressed: state.isPressed, focusVisible: state.isFocusVisible, disabled: state.isDisabled }
15
+ })), style: {
16
+ appearance: "none",
17
+ display: "grid",
18
+ placeItems: "center",
19
+ flex: "0 0 auto",
20
+ width: size,
21
+ height: size,
22
+ padding: 0,
23
+ border: 0,
24
+ background: "none",
25
+ cursor: "pointer",
26
+ ...style
27
+ }, children: children });
28
+ }
@@ -0,0 +1,20 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import { type ControlMetrics } from "./control.js";
3
+ /**
4
+ * The root of every value-holding component. It is the only element that
5
+ * applies the control text scale; every part inherits from it.
6
+ */
7
+ export declare function fieldStyle(metrics: ControlMetrics, style?: CSSProperties): CSSProperties;
8
+ export declare function FieldLabel({ label }: Readonly<{
9
+ label?: ReactNode;
10
+ }>): import("react").JSX.Element | null;
11
+ /** Description and validation text shared by every field. */
12
+ export declare function FieldFeedback({ description, errorMessage, metrics }: Readonly<{
13
+ description?: ReactNode;
14
+ errorMessage?: ReactNode;
15
+ metrics: ControlMetrics;
16
+ }>): import("react").JSX.Element;
17
+ /** How strongly every separator shows its text color. */
18
+ export declare const separatorOpacity = 0.1;
19
+ /** Separator paint shared by menus, tables, layouts, and toolbars. */
20
+ export declare function separatorColor(metrics: ControlMetrics): string;
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FieldError, Label, Text } from "react-aria-components";
3
+ import { colorOpacity, mixColor } from "../foundation/color.js";
4
+ import { controlFontWeight, controlOpacity } from "./control.js";
5
+ /**
6
+ * The root of every value-holding component. It is the only element that
7
+ * applies the control text scale; every part inherits from it.
8
+ */
9
+ export function fieldStyle(metrics, style) {
10
+ return {
11
+ display: "grid",
12
+ alignContent: "start",
13
+ gap: metrics.gap,
14
+ minWidth: 0,
15
+ fontFamily: "inherit",
16
+ fontSize: metrics.fontSize,
17
+ lineHeight: 1.45,
18
+ ...style
19
+ };
20
+ }
21
+ export function FieldLabel({ label }) {
22
+ return label == null ? null : _jsx(Label, { style: { fontWeight: controlFontWeight }, children: label });
23
+ }
24
+ /** Description and validation text shared by every field. */
25
+ export function FieldFeedback({ description, errorMessage, metrics }) {
26
+ const { colors } = metrics.visual;
27
+ return _jsxs(_Fragment, { children: [description != null && _jsx(Text, { slot: "description", style: { fontSize: "0.92em", opacity: controlOpacity.secondary }, children: description }), _jsx(FieldError, { style: { fontSize: "0.92em", color: mixColor(colors.danger, colors.foreground, 0.15) }, children: errorMessage })] });
28
+ }
29
+ /** How strongly every separator shows its text color. */
30
+ export const separatorOpacity = 0.1;
31
+ /** Separator paint shared by menus, tables, layouts, and toolbars. */
32
+ export function separatorColor(metrics) {
33
+ return colorOpacity(metrics.visual.colors.foreground, separatorOpacity);
34
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The treatment of every icon React UI draws inside its own components: a
3
+ * constant 1.5px stroke at any size, hidden from assistive technology because
4
+ * the control that holds it carries the name.
5
+ */
6
+ export declare function iconProps(size: number): {
7
+ readonly size: number;
8
+ readonly strokeWidth: 1.5;
9
+ readonly nonScalingStroke: true;
10
+ readonly "aria-hidden": true;
11
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The treatment of every icon React UI draws inside its own components: a
3
+ * constant 1.5px stroke at any size, hidden from assistive technology because
4
+ * the control that holds it carries the name.
5
+ */
6
+ export function iconProps(size) {
7
+ return { size, strokeWidth: 1.5, nonScalingStroke: true, "aria-hidden": true };
8
+ }
@@ -0,0 +1,37 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { AppearanceColors } from "../foundation/appearance.js";
3
+ import { type Color } from "../foundation/color.js";
4
+ import type { Visual } from "../foundation/visual.js";
5
+ import { type ControlMetrics } from "./control.js";
6
+ import type { SurfaceRenderProps } from "./surface-render.js";
7
+ /**
8
+ * What marks a selection, in every collection and inside a calendar range:
9
+ * the subtle level of its color under the same veil as hover. The veil keeps
10
+ * a light color apart from its canvas, so a selection never shows less
11
+ * clearly than hovering does.
12
+ */
13
+ export declare function selectionColor(base: string, colors: AppearanceColors): string;
14
+ export type ItemState = Readonly<{
15
+ selected: boolean;
16
+ hovered: boolean;
17
+ pressed: boolean;
18
+ focusVisible: boolean;
19
+ disabled: boolean;
20
+ }>;
21
+ /**
22
+ * One entry of a collection. At rest it has no paint of its own; hover and
23
+ * press veil it, and selection lays the selection color of the collection
24
+ * color beneath it. Every collection uses this same treatment.
25
+ */
26
+ export declare function itemSurface(visual: Visual, color: Color, state: ItemState, radius: CSSProperties["borderRadius"]): SurfaceRenderProps;
27
+ /** The same treatment as a plain paint, for elements that cannot host a Surface. */
28
+ export declare function itemPaint(visual: Visual, color: Color, state: ItemState): Readonly<{
29
+ background: string;
30
+ color: string;
31
+ }>;
32
+ /** Layout shared by list, menu, and tree entries. */
33
+ export declare function itemStyle(metrics: ControlMetrics, disabled: boolean): CSSProperties;
34
+ /** The selection mark drawn at the end of a selected entry. */
35
+ export declare function SelectionMark({ visible }: Readonly<{
36
+ visible: boolean;
37
+ }>): import("react").JSX.Element;
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { colorLevel, mixColor, resolveColor } from "../foundation/color.js";
3
+ import { clearVeil, interactionShift, surfacePaint } from "../surface/surface.js";
4
+ import { transition } from "./control.js";
5
+ import { Check } from "lucide-react";
6
+ import { iconProps } from "./icon.js";
7
+ /**
8
+ * What marks a selection, in every collection and inside a calendar range:
9
+ * the subtle level of its color under the same veil as hover. The veil keeps
10
+ * a light color apart from its canvas, so a selection never shows less
11
+ * clearly than hovering does.
12
+ */
13
+ export function selectionColor(base, colors) {
14
+ return mixColor(colorLevel(base, "subtle", colors), colors.foreground, interactionShift.hovered * clearVeil);
15
+ }
16
+ /**
17
+ * One entry of a collection. At rest it has no paint of its own; hover and
18
+ * press veil it, and selection lays the selection color of the collection
19
+ * color beneath it. Every collection uses this same treatment.
20
+ */
21
+ export function itemSurface(visual, color, state, radius) {
22
+ return {
23
+ color: state.selected ? selectionColor(resolveColor(color, visual.colors), visual.colors) : "transparent",
24
+ depth: "flat",
25
+ material: "none",
26
+ radius,
27
+ interaction: itemInteraction(state)
28
+ };
29
+ }
30
+ /** The same treatment as a plain paint, for elements that cannot host a Surface. */
31
+ export function itemPaint(visual, color, state) {
32
+ const paint = surfacePaint(visual, state.selected ? selectionColor(resolveColor(color, visual.colors), visual.colors) : "transparent", "flat", "none", false, itemInteraction(state));
33
+ return { background: paint.fill, color: paint.text };
34
+ }
35
+ function itemInteraction(state) {
36
+ return { hovered: state.hovered, pressed: state.pressed, focusVisible: state.focusVisible, disabled: state.disabled };
37
+ }
38
+ /** Layout shared by list, menu, and tree entries. */
39
+ export function itemStyle(metrics, disabled) {
40
+ return {
41
+ ...transition(metrics.visual, "box-shadow, color, outline-color, opacity"),
42
+ display: "flex",
43
+ alignItems: "center",
44
+ gap: metrics.gap,
45
+ minWidth: 0,
46
+ minHeight: metrics.height,
47
+ paddingInline: metrics.inset,
48
+ boxSizing: "border-box",
49
+ cursor: disabled ? "not-allowed" : "pointer",
50
+ userSelect: "none",
51
+ outlineOffset: -1
52
+ };
53
+ }
54
+ /** The selection mark drawn at the end of a selected entry. */
55
+ export function SelectionMark({ visible }) {
56
+ return _jsx(Check, { ...iconProps(14), style: { flexShrink: 0, marginInlineStart: "auto", opacity: visible ? 1 : 0 } });
57
+ }
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * Whether an overlay is shown. Its root owns this state; triggers and
4
+ * positioned parts only read it, so they never repeat it.
5
+ */
6
+ export type OpenStateProps = Readonly<{
7
+ open?: boolean;
8
+ defaultOpen?: boolean;
9
+ onOpenChange?: (open: boolean) => void;
10
+ }>;
11
+ /** The root of an overlay family: its open state and its parts. */
12
+ export type OverlayRootProps = OpenStateProps & Readonly<{
13
+ children: ReactNode;
14
+ }>;
15
+ /**
16
+ * React Aria props that overlay parts own internally, or that belong to the
17
+ * family root as open state. They never appear on React UI overlay parts.
18
+ */
19
+ export type AriaOverlayInternals = "isOpen" | "defaultOpen" | "onOpenChange" | "isEntering" | "isExiting" | "isNonModal" | "isDismissable" | "isKeyboardDismissDisabled" | "shouldCloseOnInteractOutside" | "shouldFlip" | "shouldUpdatePosition" | "shouldSkipAnimation" | "trigger" | "triggerRef" | "UNSTABLE_portalContainer" | "arrowRef" | "arrowBoundaryOffset" | "scrollRef" | "getTargetRect";
20
+ /** The one translation from React UI's open-state names to React Aria's. */
21
+ export declare function ariaOpenState({ open, defaultOpen, onOpenChange }: OpenStateProps): {
22
+ onOpenChange?: ((open: boolean) => void) | undefined;
23
+ defaultOpen?: boolean | undefined;
24
+ isOpen?: boolean | undefined;
25
+ };
@@ -0,0 +1,8 @@
1
+ /** The one translation from React UI's open-state names to React Aria's. */
2
+ export function ariaOpenState({ open, defaultOpen, onOpenChange }) {
3
+ return {
4
+ ...(open !== undefined ? { isOpen: open } : {}),
5
+ ...(defaultOpen !== undefined ? { defaultOpen } : {}),
6
+ ...(onOpenChange !== undefined ? { onOpenChange } : {})
7
+ };
8
+ }
@@ -0,0 +1,39 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import { type Color } from "../foundation/color.js";
3
+ import type { MaterialOverrides } from "../surface/material-options.js";
4
+ import { type ControlMetrics } from "./control.js";
5
+ type Orientation = "horizontal" | "vertical";
6
+ /** The track's padding around the items it holds. */
7
+ export declare const selectionTrackInset = 3;
8
+ /**
9
+ * A recessed track holding a row of mutually exclusive items, such as Tabs or
10
+ * a SegmentedControl; the selection rises out of it as one raised Surface that
11
+ * slides to whichever item is selected. The list inside marks itself with
12
+ * `data-selection-list`, and React Aria marks the selected item `data-selected`.
13
+ */
14
+ export declare function SelectionTrack({ metrics, color, material, orientation, selectedKey, className, style, children }: Readonly<{
15
+ metrics: ControlMetrics;
16
+ color: Color;
17
+ material: MaterialOverrides["material"];
18
+ orientation: Orientation;
19
+ selectedKey: unknown;
20
+ className?: string;
21
+ style?: CSSProperties;
22
+ children: ReactNode;
23
+ }>): import("react").JSX.Element;
24
+ /** Layout of the list inside a SelectionTrack: equal columns across, or a stack. */
25
+ export declare function selectionListStyle(orientation: Orientation): CSSProperties;
26
+ type ItemState = Readonly<{
27
+ isSelected: boolean;
28
+ isFocusVisible: boolean;
29
+ isDisabled: boolean;
30
+ }>;
31
+ /** One item of a SelectionTrack; its label reads against the selection when selected. */
32
+ export declare function selectionItemStyle(metrics: ControlMetrics, color: Color, orientation: Orientation, state: ItemState): CSSProperties;
33
+ /** An item's label: an optional leading icon and text, centered a gap apart. */
34
+ export declare function SelectionItemLabel({ metrics, emphasized, children }: Readonly<{
35
+ metrics: ControlMetrics;
36
+ emphasized: boolean;
37
+ children: ReactNode;
38
+ }>): import("react").JSX.Element;
39
+ export {};
@@ -0,0 +1,122 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useRef, useState } from "react";
3
+ import { colorOpacity, readableColor, resolveColor } from "../foundation/color.js";
4
+ import { Surface, SurfaceView } from "../surface/surface.js";
5
+ import { controlOpacity, transition } from "./control.js";
6
+ /** The track's padding around the items it holds. */
7
+ export const selectionTrackInset = 3;
8
+ /**
9
+ * A recessed track holding a row of mutually exclusive items, such as Tabs or
10
+ * a SegmentedControl; the selection rises out of it as one raised Surface that
11
+ * slides to whichever item is selected. The list inside marks itself with
12
+ * `data-selection-list`, and React Aria marks the selected item `data-selected`.
13
+ */
14
+ export function SelectionTrack({ metrics, color, material, orientation, selectedKey, className, style, children }) {
15
+ const track = useRef(null);
16
+ const selection = useSelectionPlacement(track, selectedKey, orientation);
17
+ return _jsxs(SurfaceView, { ref: track, color: "background", depth: "recessed", material: material, radius: metrics.radius, className: className, style: { padding: selectionTrackInset, width: orientation === "horizontal" ? "100%" : undefined, boxSizing: "border-box", ...style }, children: [selection !== null && _jsx(Surface, { "data-selection-indicator": "", "aria-hidden": "true", color: color, material: material, radius: metrics.radius, style: {
18
+ ...(selection.moving ? transition(metrics.visual, "translate, width, height") : {}),
19
+ position: "absolute",
20
+ top: 0,
21
+ left: 0,
22
+ zIndex: 1,
23
+ width: selection.width,
24
+ height: selection.height,
25
+ translate: `${selection.x}px ${selection.y}px`
26
+ } }), children] });
27
+ }
28
+ /** Layout of the list inside a SelectionTrack: equal columns across, or a stack. */
29
+ export function selectionListStyle(orientation) {
30
+ return {
31
+ position: "relative",
32
+ zIndex: 2,
33
+ display: "grid",
34
+ gridAutoFlow: orientation === "horizontal" ? "column" : "row",
35
+ gridAutoColumns: orientation === "horizontal" ? "minmax(0, 1fr)" : undefined,
36
+ alignContent: "start",
37
+ gap: 2,
38
+ minWidth: 0
39
+ };
40
+ }
41
+ /** One item of a SelectionTrack; its label reads against the selection when selected. */
42
+ export function selectionItemStyle(metrics, color, orientation, state) {
43
+ const { colors } = metrics.visual;
44
+ return {
45
+ ...transition(metrics.visual, "color, outline-color"),
46
+ position: "relative",
47
+ display: "flex",
48
+ alignItems: "center",
49
+ justifyContent: orientation === "vertical" ? "start" : "center",
50
+ minWidth: 0,
51
+ minHeight: metrics.height - selectionTrackInset * 2,
52
+ paddingInline: metrics.inset,
53
+ boxSizing: "border-box",
54
+ border: 0,
55
+ background: "none",
56
+ font: "inherit",
57
+ borderRadius: metrics.radius,
58
+ outline: `3px solid ${state.isFocusVisible ? colorOpacity(colors.primary, 0.34) : "transparent"}`,
59
+ outlineOffset: 1,
60
+ cursor: state.isDisabled ? "not-allowed" : "pointer",
61
+ userSelect: "none",
62
+ fontWeight: 500,
63
+ color: state.isSelected ? readableColor(resolveColor(color, colors), colors) : colors.foreground
64
+ };
65
+ }
66
+ /** An item's label: an optional leading icon and text, centered a gap apart. */
67
+ export function SelectionItemLabel({ metrics, emphasized, children }) {
68
+ return _jsx("span", { style: {
69
+ ...transition(metrics.visual, "opacity"),
70
+ display: "flex",
71
+ alignItems: "center",
72
+ gap: metrics.gap,
73
+ minWidth: 0,
74
+ overflow: "hidden",
75
+ whiteSpace: "nowrap",
76
+ opacity: emphasized ? 1 : controlOpacity.secondary
77
+ }, children: children });
78
+ }
79
+ /**
80
+ * Where the selected item sits inside the track. The first placement appears
81
+ * in place; later ones slide.
82
+ */
83
+ function useSelectionPlacement(track, selectedKey, orientation) {
84
+ const [placement, setPlacement] = useState(null);
85
+ useLayoutEffect(() => {
86
+ const element = track.current;
87
+ const list = element?.querySelector("[data-selection-list]");
88
+ if (element == null || list == null)
89
+ return;
90
+ const measure = () => {
91
+ const item = list.querySelector("[data-selected]");
92
+ if (item == null)
93
+ return setPlacement(null);
94
+ const outer = element.getBoundingClientRect();
95
+ const inner = item.getBoundingClientRect();
96
+ // Rectangles keep subpixel precision; dividing by the track's own
97
+ // scale undoes any transform, such as an entering overlay's.
98
+ const scale = element.offsetWidth > 0 ? outer.width / element.offsetWidth : 1;
99
+ const next = {
100
+ x: (inner.left - outer.left) / scale - element.clientLeft,
101
+ y: (inner.top - outer.top) / scale - element.clientTop,
102
+ width: inner.width / scale,
103
+ height: inner.height / scale
104
+ };
105
+ setPlacement(previous => previous !== null && previous.x === next.x && previous.y === next.y
106
+ && previous.width === next.width && previous.height === next.height
107
+ ? previous
108
+ : { ...next, moving: previous !== null });
109
+ };
110
+ measure();
111
+ // Items render after the list itself, and selection is written onto them.
112
+ const mutations = new MutationObserver(measure);
113
+ mutations.observe(list, { subtree: true, childList: true, attributeFilter: ["data-selected"] });
114
+ const resizes = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : null;
115
+ resizes?.observe(list);
116
+ return () => {
117
+ mutations.disconnect();
118
+ resizes?.disconnect();
119
+ };
120
+ }, [track, selectedKey, orientation]);
121
+ return placement;
122
+ }
@@ -0,0 +1,44 @@
1
+ import type { Key, Selection } from "react-aria-components";
2
+ export type MultipleStringSelection = readonly string[] | "all";
3
+ export type SingleStringSelectionProps = Readonly<{
4
+ value?: string | null;
5
+ defaultValue?: string | null;
6
+ onChange?: (value: string | null) => void;
7
+ }>;
8
+ export type MultipleStringSelectionProps = Readonly<{
9
+ value?: MultipleStringSelection;
10
+ defaultValue?: MultipleStringSelection;
11
+ onChange?: (value: MultipleStringSelection) => void;
12
+ }>;
13
+ export declare function toAriaSelection(value: string | null | MultipleStringSelection | undefined): "all" | Set<string> | undefined;
14
+ export declare function stringKey(value: Key): string;
15
+ export declare function optionalStringKey(value: Key | null): string | null;
16
+ export declare function singleSelection(selection: Selection): string | null;
17
+ export declare function multipleSelection(selection: Selection): MultipleStringSelection;
18
+ export declare function stringKeys(values: Iterable<Key>): readonly string[];
19
+ export type NoSelectionProps = Readonly<{
20
+ selectionMode?: "none";
21
+ value?: never;
22
+ defaultValue?: never;
23
+ onChange?: never;
24
+ }>;
25
+ export type SingleSelectionProps = SingleStringSelectionProps & Readonly<{
26
+ selectionMode: "single";
27
+ }>;
28
+ export type MultipleSelectionProps = MultipleStringSelectionProps & Readonly<{
29
+ selectionMode: "multiple";
30
+ }>;
31
+ type AnySelection = Readonly<{
32
+ selectionMode?: "none" | "single" | "multiple";
33
+ value?: string | null | MultipleStringSelection;
34
+ defaultValue?: string | null | MultipleStringSelection;
35
+ onChange?: ((value: string | null) => void) | ((value: MultipleStringSelection) => void);
36
+ }>;
37
+ /** Translates one string-valued selection contract into React Aria collection props. */
38
+ export declare function ariaSelection(properties: AnySelection, fallbackMode: "none" | "single" | "multiple"): {
39
+ onSelectionChange?: ((selection: Selection) => void) | undefined;
40
+ defaultSelectedKeys?: "all" | Set<string> | undefined;
41
+ selectedKeys?: "all" | Set<string> | undefined;
42
+ selectionMode: "none" | "multiple" | "single";
43
+ };
44
+ export {};
@@ -26,3 +26,19 @@ export function multipleSelection(selection) {
26
26
  export function stringKeys(values) {
27
27
  return [...values].map(stringKey);
28
28
  }
29
+ /** Translates one string-valued selection contract into React Aria collection props. */
30
+ export function ariaSelection(properties, fallbackMode) {
31
+ const mode = properties.selectionMode ?? fallbackMode;
32
+ const selectedKeys = toAriaSelection(properties.value);
33
+ const defaultSelectedKeys = toAriaSelection(properties.defaultValue);
34
+ const onChange = properties.onChange;
35
+ const onSelectionChange = onChange === undefined || mode === "none"
36
+ ? undefined
37
+ : (selection) => onChange(mode === "multiple" ? multipleSelection(selection) : singleSelection(selection));
38
+ return {
39
+ selectionMode: mode,
40
+ ...(selectedKeys !== undefined ? { selectedKeys } : {}),
41
+ ...(defaultSelectedKeys !== undefined ? { defaultSelectedKeys } : {}),
42
+ ...(onSelectionChange !== undefined ? { onSelectionChange } : {})
43
+ };
44
+ }
@@ -0,0 +1,10 @@
1
+ import type { ElementType, ReactElement } from "react";
2
+ import { type SurfaceInteraction, type SurfaceOwnProps } from "../surface/surface.js";
3
+ export type SurfaceRenderProps = SurfaceOwnProps & Readonly<{
4
+ interaction?: SurfaceInteraction;
5
+ }>;
6
+ /**
7
+ * Lets a React Aria component render its own DOM element as a Surface. React
8
+ * Aria keeps behavior, semantics, and state; the Surface owns every paint.
9
+ */
10
+ export declare function surfaceRender<State>(as: ElementType, derive: (state: State) => SurfaceRenderProps): (native: object, state: State) => ReactElement;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { SurfaceView } from "../surface/surface.js";
3
+ /**
4
+ * Lets a React Aria component render its own DOM element as a Surface. React
5
+ * Aria keeps behavior, semantics, and state; the Surface owns every paint.
6
+ */
7
+ export function surfaceRender(as, derive) {
8
+ return (native, state) => {
9
+ const { ref, ...properties } = native;
10
+ return _jsx(SurfaceView, { ...properties, ...derive(state), as: as, ref: ref });
11
+ };
12
+ }