@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,239 @@
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";
4
+ import { resolveRadius } from "../foundation/radius.js";
5
+ import { useVisual } from "../foundation/visual.js";
6
+ import { grainImage, grainSeed, grainSize } from "./grain.js";
7
+ import { flushPaintRules, paintClass, usePaintClasses } from "./paint-class.js";
8
+ import { resolveMaterialOptions } from "./material-options.js";
9
+ import { resolveShadowOptions, shadowStyle } from "./shadow-options.js";
10
+ /**
11
+ * The single visual primitive. Components express what they are through
12
+ * color, depth, and interaction; paint, grain, frost, edge, shadow, focus,
13
+ * and the text color are all derived here from Appearance.
14
+ */
15
+ export const SurfaceView = forwardRef(function Surface({ as: Element = "div", color = "background", depth = "raised", material, shadow, radius, interaction, className, children, style, ...properties }, ref) {
16
+ const visual = useVisual();
17
+ const identity = useId();
18
+ const paint = surfacePaint(visual, color, depth, material, shadow, interaction);
19
+ const borderRadius = radius === undefined && style?.borderRadius !== undefined
20
+ ? style.borderRadius
21
+ : resolveRadius(radius ?? "medium", visual.radius);
22
+ const declarations = [
23
+ ...Object.entries(paint.variables).map(([name, value]) => `${name}: ${value}`),
24
+ "transition-property: box-shadow, color, outline-color, opacity",
25
+ `transition-duration: ${visual.duration}ms`,
26
+ `transition-timing-function: ${visual.easing}`,
27
+ `box-shadow: ${paint.shadow}`,
28
+ `color: ${paint.text}`,
29
+ `outline: 3px solid ${interaction?.focusVisible ? paint.ring : "transparent"}`,
30
+ "outline-offset: 1px",
31
+ `border-radius: ${typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius}`
32
+ ].join("; ");
33
+ const painted = usePaintClasses() ? paintClass(declarations) : undefined;
34
+ useInsertionEffect(flushPaintRules);
35
+ return createElement(Element, {
36
+ ...properties,
37
+ ref,
38
+ className: [surfaceClass, painted, dimmedClass(interaction?.disabled ?? false), className].filter(Boolean).join(" "),
39
+ // Without a document (server rendering), the same declarations travel inline.
40
+ style: painted === undefined ? { ...inlineDeclarations(declarations), ...style } : style
41
+ }, _jsx(SurfaceStyle, {}), paint.distortion > 0 && _jsx(Refraction, { identity: identity, distortion: paint.distortion }), children);
42
+ });
43
+ function inlineDeclarations(declarations) {
44
+ const result = {};
45
+ for (const declaration of declarations.split("; ")) {
46
+ const separator = declaration.indexOf(": ");
47
+ const name = declaration.slice(0, separator);
48
+ result[name.startsWith("--") ? name : name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())] = declaration.slice(separator + 2);
49
+ }
50
+ return result;
51
+ }
52
+ /** One material- and depth-owning element. The host is a div unless `as` selects another. */
53
+ export const Surface = SurfaceView;
54
+ /**
55
+ * Marks an element that is disabled. Disabling fades only the outermost
56
+ * disabled element: parts inside it, such as a field's well or a disabled
57
+ * calendar's days, are disabled with it and do not fade a second time.
58
+ */
59
+ export function dimmedClass(disabled, className) {
60
+ return [disabled ? dimmed : undefined, className].filter(Boolean).join(" ") || undefined;
61
+ }
62
+ const disabledOpacity = 0.46;
63
+ const dimmed = "phreshos-dimmed";
64
+ const surfaceClass = "phreshos-surface";
65
+ const materialLevel = Object.freeze({ none: 0, basic: 1, extended: 2, full: 3 });
66
+ /** How far hover and press move a paint toward the text color. */
67
+ export const interactionShift = Object.freeze({ hovered: 0.05, pressed: 0.1 });
68
+ /** A clear Surface has no paint to move, so it shows the same shift as a slightly stronger veil. */
69
+ export const clearVeil = 1.3;
70
+ /** Resolves every visual value of one Surface. Pure over its inputs. */
71
+ export function surfacePaint(visual, color, depth, options, shadowOptions, interaction) {
72
+ const { colors } = visual;
73
+ const base = resolveColor(color, colors);
74
+ const active = interaction !== undefined && !interaction.disabled;
75
+ const shift = active ? interaction.pressed ? interactionShift.pressed : interaction.hovered ? interactionShift.hovered : 0 : 0;
76
+ // Recessed paint sinks slightly below its surroundings; interaction then
77
+ // moves the same base toward the content color so every state stays related.
78
+ const darkness = canvasDarkness(colors);
79
+ // A recess moves away from its surroundings toward whichever extreme has
80
+ // room, by one constant perceptual difference on any canvas.
81
+ const rest = depth === "recessed" ? recessColor(base) : base;
82
+ // A transparent Surface has no substance of its own: it takes the content
83
+ // color of whatever it sits on and only reveals interaction as a veil.
84
+ const clear = base === "transparent";
85
+ const fill = clear
86
+ ? shift > 0 ? colorOpacity(colors.foreground, shift * clearVeil) : "transparent"
87
+ : mixColor(rest, colors.foreground, shift);
88
+ const mode = clear ? "none" : typeof options === "object" ? "full" : options ?? "basic";
89
+ const level = materialLevel[mode];
90
+ const material = resolveMaterialOptions(typeof options === "object" ? options : {}, visual.material);
91
+ const opacity = level >= materialLevel.extended ? material.opacity : 1;
92
+ const translucent = opacity < 1;
93
+ const frost = level >= materialLevel.full && translucent
94
+ ? [material.backdrop > 0 ? `blur(${material.backdrop}px)` : "", material.saturation !== 1 ? `saturate(${material.saturation})` : ""].filter(Boolean).join(" ")
95
+ : "";
96
+ const grain = level >= materialLevel.basic && material.grain > 0 && material.grainAmount > 0
97
+ ? grainImage(grainSeed, material.grainAmount, level === materialLevel.basic ? material.grain * material.opacity : material.grain)
98
+ : "none";
99
+ const ringBase = interaction?.invalid
100
+ ? colors.danger
101
+ : !clear && (contrast(base, colors.background) ?? 1) >= 1.6 ? base : colors.primary;
102
+ const edged = level >= materialLevel.basic;
103
+ const lit = edgeLight(depth, darkness);
104
+ const light = mixColor(fill, "#ffffff", 0.7);
105
+ // Light rises from the middle of the top and bottom edges and spreads along
106
+ // them, fading as it goes, so it leans toward the sides without reaching
107
+ // them; the same slight spill falls inward from both middles. A hairline
108
+ // outside separates the Surface from whatever it sits on. Neither edge
109
+ // dominates, so no Surface reads as a bevel. Depth changes only how much
110
+ // light the rim receives.
111
+ const rim = edged
112
+ ? `linear-gradient(to right, transparent, ${colorOpacity(light, lit.side)} 18%, ${colorOpacity(light, lit.edge)} 50%, ${colorOpacity(light, lit.side)} 82%, transparent)`
113
+ : "none";
114
+ // The spill reaches at most a fixed depth, and less on short Surfaces: at a
115
+ // fixed depth, a small button was lit through nearly half its height and
116
+ // read as swollen, while a large panel barely changes.
117
+ const spill = (edge) => edged && lit.spill > 0
118
+ ? `radial-gradient(50% ${spillDepth} at 50% ${edge === "top" ? "0" : "100%"}, ${colorOpacity(light, lit.spill)}, transparent)`
119
+ : "none";
120
+ // A recessed Surface holds a value, so focus and validity also claim its hairline.
121
+ const claimed = interaction?.invalid || (depth === "recessed" && interaction?.focusVisible);
122
+ const hairline = !edged
123
+ ? null
124
+ : claimed
125
+ ? `0 0 0 1px ${ringBase}`
126
+ : `0 0 0 0.5px ${colorOpacity("#000000", lit.hairline)}`;
127
+ const shadow = visual.shadow;
128
+ const outer = clear || depth !== "raised" || shadowOptions === false || (active && interaction.pressed)
129
+ ? null
130
+ : shadowStyle(resolveShadowOptions(typeof shadowOptions === "object" ? shadowOptions : {}, shadow));
131
+ return {
132
+ fill,
133
+ // The label is chosen once for the resting paint: interaction shades stay
134
+ // close to it, and re-deciding per state would flip text on near-ties.
135
+ text: clear ? "inherit" : readableColor(rest, colors),
136
+ ring: colorOpacity(ringBase, 0.34),
137
+ shadow: [hairline, outer === "none" ? null : outer].filter(Boolean).join(", ") || "none",
138
+ distortion: level >= materialLevel.full && translucent ? material.distortion : 0,
139
+ variables: {
140
+ "--phreshos-surface-paint": translucent ? colorOpacity(fill, opacity) : fill,
141
+ "--phreshos-surface-grain": grain,
142
+ "--phreshos-surface-frost": frost || "none",
143
+ "--phreshos-surface-rim": rim,
144
+ "--phreshos-surface-spill-top": spill("top"),
145
+ "--phreshos-surface-spill-bottom": spill("bottom"),
146
+ "--phreshos-surface-duration": `${visual.duration}ms`,
147
+ "--phreshos-surface-easing": visual.easing
148
+ }
149
+ };
150
+ }
151
+ /** How far the inward spill reaches: a fixed depth, or a share of a short Surface's height. */
152
+ const spillDepth = "min(6px, 12%)";
153
+ /** How much light each part of the rim receives, interpolated along the canvas darkness. */
154
+ function edgeLight(depth, darkness) {
155
+ const { light, dark } = edgeLightEnds[depth];
156
+ return {
157
+ edge: round(mix(light.edge, dark.edge, darkness)),
158
+ side: round(mix(light.side, dark.side, darkness)),
159
+ spill: round(mix(light.spill, dark.spill, darkness)),
160
+ hairline: round(mix(light.hairline, dark.hairline, darkness))
161
+ };
162
+ }
163
+ function round(value) {
164
+ return Math.round(value * 1_000) / 1_000;
165
+ }
166
+ function mix(from, to, amount) {
167
+ return from + (to - from) * amount;
168
+ }
169
+ const edgeLightEnds = Object.freeze({
170
+ raised: {
171
+ light: { edge: 0.55, side: 0.22, spill: 0.15, hairline: 0.16 },
172
+ dark: { edge: 0.18, side: 0.08, spill: 0.035, hairline: 0.55 }
173
+ },
174
+ flat: {
175
+ light: { edge: 0.3, side: 0.14, spill: 0, hairline: 0.12 },
176
+ dark: { edge: 0.1, side: 0.05, spill: 0, hairline: 0.45 }
177
+ },
178
+ recessed: {
179
+ light: { edge: 0.45, side: 0.12, spill: 0, hairline: 0.12 },
180
+ dark: { edge: 0.12, side: 0.04, spill: 0, hairline: 0.45 }
181
+ }
182
+ });
183
+ /** A separate compositor pass for refraction, present only when it is visible. */
184
+ function Refraction({ identity, distortion }) {
185
+ const filter = `phreshos-refraction-${identity.replaceAll(":", "")}`;
186
+ return _jsx("span", { "aria-hidden": "true", "data-surface-refraction": "", style: {
187
+ position: "absolute",
188
+ inset: 0,
189
+ zIndex: -2,
190
+ borderRadius: "inherit",
191
+ pointerEvents: "none",
192
+ backdropFilter: `url("#${filter}")`,
193
+ WebkitBackdropFilter: `url("#${filter}")`
194
+ }, children: _jsx("svg", { width: "0", height: "0", focusable: "false", style: { position: "absolute" }, children: _jsxs("filter", { id: filter, x: "-20%", y: "-20%", width: "140%", height: "140%", colorInterpolationFilters: "sRGB", children: [_jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "0.008 0.008", numOctaves: 2, seed: 92, result: "noise" }), _jsx("feGaussianBlur", { in: "noise", stdDeviation: 2, result: "soft" }), _jsx("feDisplacementMap", { in: "SourceGraphic", in2: "soft", scale: distortion, xChannelSelector: "R", yChannelSelector: "G" })] }) }) });
195
+ }
196
+ /*
197
+ * Paint and edge live on pseudo-elements driven by per-Surface custom
198
+ * properties, so a Surface is one DOM element regardless of its Material.
199
+ * Position and isolation use zero specificity so any consumer class or style
200
+ * can replace them.
201
+ */
202
+ const stylesheet = `
203
+ :where(.${dimmed}:not(.${dimmed} .${dimmed})) {
204
+ opacity: ${disabledOpacity};
205
+ }
206
+ :where(.${surfaceClass}) {
207
+ position: relative;
208
+ isolation: isolate;
209
+ }
210
+ .${surfaceClass}::before,
211
+ .${surfaceClass}::after {
212
+ content: "";
213
+ position: absolute;
214
+ inset: 0;
215
+ border-radius: inherit;
216
+ pointer-events: none;
217
+ }
218
+ .${surfaceClass}::before {
219
+ z-index: -1;
220
+ background-color: var(--phreshos-surface-paint);
221
+ background-image: var(--phreshos-surface-spill-top), var(--phreshos-surface-spill-bottom), var(--phreshos-surface-grain);
222
+ background-size: auto, auto, ${grainSize}px ${grainSize}px;
223
+ -webkit-backdrop-filter: var(--phreshos-surface-frost);
224
+ backdrop-filter: var(--phreshos-surface-frost);
225
+ transition: background-color var(--phreshos-surface-duration) var(--phreshos-surface-easing);
226
+ }
227
+ .${surfaceClass}::after {
228
+ z-index: 1;
229
+ padding: 0.5px;
230
+ background: var(--phreshos-surface-rim);
231
+ -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
232
+ -webkit-mask-composite: xor;
233
+ mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
234
+ }
235
+ `;
236
+ /** React hoists and deduplicates this stylesheet, including inside portals. */
237
+ function SurfaceStyle() {
238
+ return _jsx("style", { href: "phreshos-react-ui-surface", precedence: "phreshos", children: stylesheet });
239
+ }
package/dist/switch.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import type { SwitchFieldProps } from "react-aria-components";
2
- import type { ControlOverrides, ControlProps, FieldProps } from "./control.js";
3
- import type { MaterialOverrides } from "./material-options.js";
4
- import type { ShadowOverrides } from "./shadow-options.js";
5
- export interface SwitchProps extends Omit<SwitchFieldProps, ControlOverrides | "isReadOnly" | "isSelected" | "defaultSelected">, ControlProps, FieldProps, MaterialOverrides, ShadowOverrides {
2
+ import { type ControlOverrides, type ControlProps, type FieldProps } from "./control/control.js";
3
+ import type { MaterialOverrides } from "./surface/material-options.js";
4
+ export interface SwitchProps extends Omit<SwitchFieldProps, ControlOverrides | "isSelected" | "defaultSelected">, ControlProps, FieldProps, MaterialOverrides {
6
5
  readonly checked?: boolean;
7
6
  readonly defaultChecked?: boolean;
8
7
  readonly readOnly?: boolean;
9
8
  }
10
- /** An on/off setting. onChange receives the next boolean value. */
9
+ /** An on/off setting. `onChange` receives the next boolean value. */
11
10
  export declare const Switch: import("react").ForwardRefExoticComponent<SwitchProps & import("react").RefAttributes<HTMLDivElement>>;
package/dist/switch.js CHANGED
@@ -1,12 +1,21 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
- import { SwitchField, SwitchButton } from "react-aria-components";
4
- import { FieldFeedback, fieldStyle, useControlTheme } from "./control.js";
5
- import { ToggleIndicator, toggleStyle } from "./toggle-indicator.js";
6
- import { resolveDirection, useDirection } from "./direction.js";
7
- /** An on/off setting. onChange receives the next boolean value. */
8
- export const Switch = forwardRef(function Switch({ label, description, errorMessage, disabled, required, invalid, readOnly, checked, defaultChecked, size, color, style, material, shadow, ...properties }, ref) {
9
- const theme = useControlTheme({ size, color });
3
+ import { SwitchButton, SwitchField } from "react-aria-components";
4
+ import { useControlMetrics } from "./control/control.js";
5
+ import { FieldFeedback, fieldStyle } from "./control/field.js";
6
+ import { ToggleIndicator, toggleRowStyle } from "./control/toggle.js";
7
+ import { resolveDirection, useDirection } from "./foundation/direction.js";
8
+ import { dimmedClass } from "./surface/surface.js";
9
+ /** An on/off setting. `onChange` receives the next boolean value. */
10
+ export const Switch = forwardRef(function Switch({ label, description, errorMessage, disabled, required, invalid, readOnly, className, checked, defaultChecked, size, color = "primary", style, material, ...properties }, ref) {
11
+ const metrics = useControlMetrics(size);
10
12
  const direction = resolveDirection(properties.dir, useDirection());
11
- return _jsxs(SwitchField, { ...properties, ref: ref, isDisabled: disabled, isRequired: required, isInvalid: invalid, isReadOnly: readOnly, isSelected: checked, defaultSelected: defaultChecked, style: state => fieldStyle(theme, state.isDisabled, style), children: [_jsx(SwitchButton, { style: state => toggleStyle(theme, state.isDisabled, state.isReadOnly), children: state => _jsxs(_Fragment, { children: [_jsx(ToggleIndicator, { kind: "switch", direction: direction, material: material, shadow: shadow, theme: theme, selected: state.isSelected, focused: state.isFocusVisible, invalid: state.isInvalid, hovered: !state.isDisabled && !state.isReadOnly && state.isHovered, pressed: !state.isDisabled && !state.isReadOnly && state.isPressed }), label] }) }), _jsx(FieldFeedback, { theme: theme, description: description, errorMessage: errorMessage })] });
13
+ return _jsxs(SwitchField, { ...properties, ref: ref, isDisabled: disabled, isRequired: required, isInvalid: invalid, isReadOnly: readOnly, isSelected: checked, defaultSelected: defaultChecked, className: state => dimmedClass(state.isDisabled, className ?? state.defaultClassName) ?? "", style: fieldStyle(metrics, style), children: [_jsx(SwitchButton, { style: state => toggleRowStyle(metrics, state.isDisabled, state.isReadOnly), children: state => _jsxs(_Fragment, { children: [_jsx(ToggleIndicator, { kind: "switch", direction: direction, color: color, material: material, metrics: metrics, state: {
14
+ selected: state.isSelected,
15
+ hovered: !state.isReadOnly && state.isHovered,
16
+ pressed: !state.isReadOnly && state.isPressed,
17
+ focusVisible: state.isFocusVisible,
18
+ invalid: state.isInvalid,
19
+ disabled: state.isDisabled
20
+ } }), label] }) }), _jsx(FieldFeedback, { metrics: metrics, description: description, errorMessage: errorMessage })] });
12
21
  });
package/dist/table.d.ts CHANGED
@@ -1,71 +1,68 @@
1
1
  import type { CSSProperties, ReactElement, RefAttributes } from "react";
2
2
  import type { CellProps as AriaCellProps, ColumnProps as AriaColumnProps, RowProps as AriaRowProps, TableBodyProps as AriaTableBodyProps, TableHeaderProps as AriaTableHeaderProps, TableProps as AriaTableProps } from "react-aria-components";
3
- import type { ControlColor } from "./control.js";
4
- import type { RadiusProps } from "./radius.js";
5
- import type { ScaleLevel } from "./scale.js";
6
- import { type MultipleStringSelection, type MultipleStringSelectionProps, type SingleStringSelectionProps } from "./selection.js";
3
+ import { type MultipleSelectionProps, type MultipleStringSelection, type NoSelectionProps, type SingleSelectionProps } from "./control/selection.js";
4
+ import { type Color } from "./foundation/color.js";
5
+ import type { RadiusProps } from "./foundation/radius.js";
6
+ import type { ScaleLevel } from "./foundation/scale.js";
7
7
  export type TableSortDirection = "ascending" | "descending";
8
8
  export interface TableSort {
9
9
  readonly column: string;
10
10
  readonly direction: TableSortDirection;
11
11
  }
12
- type TableRootBaseProps = Omit<AriaTableProps, "className" | "defaultSelectedKeys" | "disabledKeys" | "onRowAction" | "onSelectionChange" | "onSortChange" | "selectedKeys" | "selectionMode" | "sortDescriptor" | "style"> & RadiusProps & Readonly<{
12
+ type TableRootBaseProps = Omit<AriaTableProps, "className" | "defaultSelectedKeys" | "disabledKeys" | "onRowAction" | "onSelectionChange" | "onSortChange" | "selectedKeys" | "selectionMode" | "sortDescriptor" | "style" | "expandedKeys" | "defaultExpandedKeys" | "onExpandedChange" | "treeColumn" | "shouldSelectOnPressUp"> & RadiusProps & Readonly<{
13
13
  className?: string;
14
- color?: ControlColor;
15
- disabledValues?: readonly string[];
16
- onRowAction?: (value: string) => void;
14
+ /** Color laid beneath selected Rows. */
15
+ color?: Color;
16
+ /** Runs with the value of the Row that was activated. */
17
+ onAction?: (value: string) => void;
17
18
  onSortChange?: (sort: TableSort) => void;
19
+ /** Whether a Row is selected when the press ends instead of when it starts. */
20
+ selectOnPressUp?: boolean;
18
21
  size?: ScaleLevel;
19
22
  sort?: TableSort;
20
23
  style?: CSSProperties;
21
24
  }>;
22
- export type TableNoSelectionProps = Readonly<{
23
- selectionMode?: "none";
24
- value?: never;
25
- defaultValue?: never;
26
- onChange?: never;
27
- }>;
28
- export type TableSingleSelectionProps = SingleStringSelectionProps & Readonly<{
29
- selectionMode: "single";
30
- }>;
25
+ export type TableNoSelectionProps = NoSelectionProps;
26
+ export type TableSingleSelectionProps = SingleSelectionProps;
31
27
  export type TableMultipleValue = MultipleStringSelection;
32
- export type TableMultipleSelectionProps = MultipleStringSelectionProps & Readonly<{
33
- selectionMode: "multiple";
34
- }>;
28
+ export type TableMultipleSelectionProps = MultipleSelectionProps;
35
29
  export type TableRootProps = TableRootBaseProps & (TableNoSelectionProps | TableSingleSelectionProps | TableMultipleSelectionProps);
36
- export declare const TableRoot: import("react").ForwardRefExoticComponent<TableRootProps & RefAttributes<HTMLDivElement | HTMLTableElement>>;
37
30
  export interface TableHeaderProps<T extends object = object> extends Omit<AriaTableHeaderProps<T>, "className" | "style"> {
38
31
  readonly className?: string;
39
- readonly color?: ControlColor;
40
32
  readonly style?: CSSProperties;
41
33
  }
42
34
  export type TableHeaderComponent = <T extends object = object>(properties: TableHeaderProps<T> & RefAttributes<HTMLTableSectionElement | HTMLDivElement>) => ReactElement | null;
43
- export declare const TableHeader: TableHeaderComponent;
44
- export interface TableColumnProps extends Omit<AriaColumnProps, "className" | "id" | "style"> {
45
- readonly className?: string;
35
+ export interface TableColumnProps extends Omit<AriaColumnProps, "allowsSorting" | "allowsArrowNavigation" | "className" | "id" | "isRowHeader" | "style"> {
36
+ /** Whether arrow keys move between cells while focus is inside this one. */
37
+ readonly arrowNavigation?: boolean;
38
+ /** Identity of this Column, reported in sort changes. */
46
39
  readonly id: string;
40
+ /** Whether cells in this Column label their Row. */
41
+ readonly rowHeader?: boolean;
42
+ /** Whether pressing this Column requests a sort. */
43
+ readonly sortable?: boolean;
44
+ readonly className?: string;
47
45
  readonly style?: CSSProperties;
48
46
  }
49
- export declare const TableColumn: import("react").ForwardRefExoticComponent<TableColumnProps & RefAttributes<HTMLDivElement | HTMLTableCellElement>>;
50
47
  export interface TableBodyProps<T extends object = object> extends Omit<AriaTableBodyProps<T>, "className" | "style"> {
51
48
  readonly className?: string;
52
49
  readonly style?: CSSProperties;
53
50
  }
54
51
  export type TableBodyComponent = <T extends object = object>(properties: TableBodyProps<T> & RefAttributes<HTMLTableSectionElement | HTMLDivElement>) => ReactElement | null;
55
- export declare const TableBody: TableBodyComponent;
56
- export interface TableRowProps<T extends object = object> extends Omit<AriaRowProps<T>, "className" | "id" | "isDisabled" | "style"> {
52
+ export interface TableRowProps<T extends object = object> extends Omit<AriaRowProps<T>, "className" | "id" | "isDisabled" | "style" | "hasChildItems"> {
53
+ /** Identity of this Row within its Table. */
54
+ readonly id: string;
57
55
  readonly className?: string;
58
56
  readonly disabled?: boolean;
59
- readonly id: string;
60
57
  readonly style?: CSSProperties;
61
58
  }
62
59
  export type TableRowComponent = <T extends object = object>(properties: TableRowProps<T> & RefAttributes<HTMLTableRowElement | HTMLDivElement>) => ReactElement | null;
63
- export declare const TableRow: TableRowComponent;
64
- export interface TableCellProps extends Omit<AriaCellProps, "className" | "style"> {
60
+ export interface TableCellProps extends Omit<AriaCellProps, "className" | "style" | "allowsArrowNavigation"> {
61
+ /** Whether arrow keys move between cells while focus is inside this one. */
62
+ readonly arrowNavigation?: boolean;
65
63
  readonly className?: string;
66
64
  readonly style?: CSSProperties;
67
65
  }
68
- export declare const TableCell: import("react").ForwardRefExoticComponent<TableCellProps & RefAttributes<HTMLDivElement | HTMLTableCellElement>>;
69
66
  /** Structured rows and columns with optional selection and consumer-owned sorting. */
70
67
  export declare const Table: import("react").ForwardRefExoticComponent<TableRootProps & RefAttributes<HTMLDivElement | HTMLTableElement>> & {
71
68
  Header: TableHeaderComponent;
package/dist/table.js CHANGED
@@ -1,148 +1,110 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createContext, forwardRef, useContext, useMemo } 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
- import { colorOpacity } from "./color.js";
5
- import { controlFontWeight, controlOpacity, useControlTheme } from "./control.js";
6
- import { resolveDirection, useDirection } from "./direction.js";
7
- import { multipleSelection, singleSelection, stringKey, toAriaSelection } from "./selection.js";
8
- import { AriaDirectionBoundary } from "./aria-direction.js";
9
- const TableThemeContext = createContext(null);
10
- export const TableRoot = forwardRef(function TableRoot(properties, ref) {
11
- const { className, color, defaultValue, disabledValues, onChange, onRowAction, onSortChange, radius = "medium", selectionMode = "none", size = "medium", sort, style, value, ...native } = properties;
12
- const theme = useControlTheme({ color, radius, size });
13
- const context = useMemo(() => ({ color, interactive: selectionMode !== "none" || onRowAction != null, radius, size, theme }), [color, onRowAction, radius, selectionMode, size, theme]);
4
+ import { controlFontWeight, controlOpacity, transition, useControlMetrics } from "./control/control.js";
5
+ import { separatorColor } from "./control/field.js";
6
+ import { itemPaint } from "./control/item.js";
7
+ import { ariaSelection, stringKey } from "./control/selection.js";
8
+ import { AriaDirectionBoundary } from "./foundation/aria-direction.js";
9
+ import { colorOpacity } from "./foundation/color.js";
10
+ import { resolveDirection, useDirection } from "./foundation/direction.js";
11
+ import { dimmedClass } from "./surface/surface.js";
12
+ import { ChevronUp } from "lucide-react";
13
+ import { iconProps } from "./control/icon.js";
14
+ const TableStyleContext = createContext(null);
15
+ /** Structured rows and columns with optional selection and consumer-owned sorting. */
16
+ const TableRoot = forwardRef(function Table(properties, ref) {
17
+ const { className, color = "primary", defaultValue: _defaultValue, onAction, onChange: _onChange, onSortChange, radius, selectOnPressUp, selectionMode = "none", size, sort, style, value: _value, ...native } = properties;
18
+ const metrics = useControlMetrics(size, radius);
19
+ const context = useMemo(() => ({ color, interactive: selectionMode !== "none" || onAction != null, metrics }), [color, metrics, onAction, selectionMode]);
14
20
  const direction = resolveDirection(native.dir, useDirection());
15
- return _jsx(TableThemeContext.Provider, { value: context, children: _jsx(AriaDirectionBoundary, { direction: direction, children: _jsx(AriaTable, { ...native, ...selectionProperties(properties), ...(sort === undefined ? {} : { sortDescriptor: sort }), ref: ref, dir: direction, className: className, disabledKeys: disabledValues, selectionMode: selectionMode, onRowAction: onRowAction == null ? undefined : key => onRowAction(stringKey(key)), onSortChange: onSortChange == null ? undefined : descriptor => onSortChange(tableSort(descriptor)), style: {
21
+ 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: {
16
22
  width: "100%",
17
23
  minWidth: "max-content",
18
24
  borderCollapse: "separate",
19
25
  borderSpacing: 0,
20
26
  boxSizing: "border-box",
21
- borderRadius: theme.radius,
22
- // Header and row paints belong to the table, so the table must clip
23
- // them rather than relying on a surrounding surface for its radius.
27
+ // Row paints belong to the table, so the table clips them to its own radius.
28
+ borderRadius: metrics.radius,
24
29
  overflow: "hidden",
25
30
  fontFamily: "inherit",
26
- fontSize: theme.fontSize,
31
+ fontSize: metrics.fontSize,
27
32
  ...style
28
33
  } }) }) });
29
34
  });
30
- function selectionProperties(properties) {
31
- const selectedKeys = toAriaSelection(properties.value);
32
- const defaultSelectedKeys = toAriaSelection(properties.defaultValue);
33
- const onSelectionChange = properties.onChange == null
34
- ? undefined
35
- : (selection) => {
36
- if (properties.selectionMode === "multiple") {
37
- properties.onChange?.(multipleSelection(selection));
38
- return;
39
- }
40
- if (properties.selectionMode === "single")
41
- properties.onChange?.(singleSelection(selection));
42
- };
43
- return {
44
- ...(selectedKeys !== undefined ? { selectedKeys } : {}),
45
- ...(defaultSelectedKeys !== undefined ? { defaultSelectedKeys } : {}),
46
- ...(onSelectionChange !== undefined ? { onSelectionChange } : {})
47
- };
48
- }
49
35
  function tableSort(descriptor) {
50
36
  return { column: stringKey(descriptor.column), direction: descriptor.direction };
51
37
  }
52
- const TableHeaderImplementation = forwardRef(function TableHeader({ className, color, style, ...properties }, ref) {
53
- const inherited = useTableContext();
54
- const resolvedColor = color ?? inherited.color;
55
- const theme = useControlTheme({ color: resolvedColor, radius: inherited.radius, size: inherited.size });
56
- const context = useMemo(() => ({ ...inherited, color: resolvedColor, theme }), [inherited, resolvedColor, theme]);
57
- return _jsx(TableThemeContext.Provider, { value: context, children: _jsx(AriaTableHeader, { ...properties, ref: ref, className: className, style: { ...style } }) });
38
+ const TableHeaderImplementation = forwardRef(function TableHeader({ className, style, ...properties }, ref) {
39
+ useTableStyle();
40
+ return _jsx(AriaTableHeader, { ...properties, ref: ref, className: className, style: style });
58
41
  });
59
- export const TableHeader = TableHeaderImplementation;
60
- export const TableColumn = forwardRef(function TableColumn({ allowsSorting = false, children, className, style, textValue, ...properties }, ref) {
61
- const theme = useTableTheme();
62
- return _jsx(AriaColumn, { ...properties, ref: ref, allowsSorting: allowsSorting, className: className, textValue: textValue ?? (typeof children === "string" ? children : undefined), style: state => ({
63
- ...theme.transition,
64
- minWidth: 0,
65
- height: theme.height,
66
- paddingBlock: theme.gap,
67
- paddingInline: Math.max(8, theme.spacing),
42
+ const TableColumn = forwardRef(function TableColumn({ sortable = false, rowHeader, arrowNavigation, children, className, style, textValue, ...properties }, ref) {
43
+ const { metrics } = useTableStyle();
44
+ const { colors } = metrics.visual;
45
+ return _jsx(AriaColumn, { ...properties, allowsArrowNavigation: arrowNavigation, ref: ref, isRowHeader: rowHeader, allowsSorting: sortable, className: className, textValue: textValue ?? (typeof children === "string" ? children : undefined), style: state => ({
46
+ ...transition(metrics.visual, "color, outline-color"),
47
+ height: metrics.height,
48
+ paddingInline: metrics.inset,
68
49
  boxSizing: "border-box",
69
- outline: state.isFocusVisible ? `1px solid ${theme.focusColor}` : "none",
70
- outlineOffset: -1,
71
- ...(state.isHovered && allowsSorting ? theme.paints.palette.hover : theme.paints.palette.rest),
72
- cursor: allowsSorting ? "pointer" : "default",
50
+ outline: `3px solid ${state.isFocusVisible ? colorOpacity(colors.primary, 0.34) : "transparent"}`,
51
+ outlineOffset: -3,
52
+ cursor: sortable ? "pointer" : "default",
53
+ fontSize: "0.92em",
73
54
  fontWeight: controlFontWeight,
74
55
  textAlign: "start",
75
56
  userSelect: "none",
57
+ opacity: state.isHovered && sortable ? 1 : controlOpacity.secondary,
76
58
  ...style
77
- }), children: state => _jsxs("span", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: theme.gap, minWidth: 0 }, children: [_jsx("span", { style: { minWidth: 0 }, children: typeof children === "function" ? children(state) : children }), allowsSorting && state.sortDirection != null && _jsx(SortIndicator, { direction: state.sortDirection, theme: theme })] }) });
59
+ }), 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" } })] }) });
78
60
  });
79
- function SortIndicator({ direction, theme }) {
80
- return _jsx("svg", { "aria-hidden": "true", width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", style: {
81
- ...theme.transition,
82
- flexShrink: 0,
83
- transitionProperty: "transform",
84
- transform: `rotate(${direction === "descending" ? 180 : 0}deg)`
85
- }, children: _jsx("path", { d: "m2 8 4-4 4 4" }) });
86
- }
87
61
  const TableBodyImplementation = forwardRef(function TableBody({ className, style, ...properties }, ref) {
88
- return _jsx(AriaTableBody, { ...properties, ref: ref, className: className, style: { ...style } });
62
+ return _jsx(AriaTableBody, { ...properties, ref: ref, className: className, style: style });
89
63
  });
90
- export const TableBody = TableBodyImplementation;
64
+ /** Rows use the collection Item treatment as their own paint. */
91
65
  const TableRowImplementation = forwardRef(function TableRow({ className, disabled = false, onAction, style, ...properties }, ref) {
92
- const { interactive, theme } = useTableContext();
93
- return _jsx(AriaRow, { ...properties, ref: ref, className: className, isDisabled: disabled, onAction: onAction, style: state => ({
94
- ...theme.transition,
95
- outline: state.isFocusVisible ? `1px solid ${theme.focusColor}` : "none",
96
- outlineOffset: -1,
97
- cursor: disabled ? "not-allowed" : interactive || onAction != null ? "pointer" : "default",
98
- opacity: disabled ? controlOpacity.disabled : 1,
99
- ...(state.isSelected
100
- ? state.isPressed
101
- ? theme.paints.palette.pressed
102
- : state.isHovered
103
- ? theme.paints.palette.hover
104
- : theme.paints.palette.rest
105
- : state.isHovered && (interactive || onAction != null)
106
- ? theme.paints.subtle.hover
107
- : { background: "transparent", color: "inherit" }),
66
+ const { color, interactive, metrics } = useTableStyle();
67
+ const responds = interactive || onAction != null;
68
+ return _jsx(AriaRow, { ...properties, ref: ref, className: state => dimmedClass(state.isDisabled, className ?? state.defaultClassName) ?? "", isDisabled: disabled, onAction: onAction, style: state => ({
69
+ ...transition(metrics.visual, "background-color, color, outline-color"),
70
+ ...itemPaint(metrics.visual, color, {
71
+ selected: state.isSelected,
72
+ hovered: responds && state.isHovered,
73
+ pressed: responds && state.isPressed,
74
+ focusVisible: false,
75
+ disabled: state.isDisabled
76
+ }),
77
+ outline: `3px solid ${state.isFocusVisible ? colorOpacity(metrics.visual.colors.primary, 0.34) : "transparent"}`,
78
+ outlineOffset: -3,
79
+ cursor: state.isDisabled ? "not-allowed" : responds ? "pointer" : "default",
108
80
  ...style
109
81
  }) });
110
82
  });
111
- export const TableRow = TableRowImplementation;
112
- export const TableCell = forwardRef(function TableCell({ className, style, ...properties }, ref) {
113
- const theme = useTableTheme();
114
- const separator = colorOpacity(theme.tint, controlOpacity.separator);
115
- return _jsx(AriaCell, { ...properties, ref: ref, className: className, style: state => ({
116
- minWidth: 0,
117
- height: theme.height,
118
- paddingBlock: theme.gap,
119
- paddingInline: Math.max(8, theme.spacing),
83
+ const TableCell = forwardRef(function TableCell({ arrowNavigation, className, style, ...properties }, ref) {
84
+ const { metrics } = useTableStyle();
85
+ return _jsx(AriaCell, { ...properties, allowsArrowNavigation: arrowNavigation, ref: ref, className: className, style: state => ({
86
+ height: metrics.height,
87
+ paddingInline: metrics.inset,
120
88
  boxSizing: "border-box",
121
- // Separators belong to the row that follows them. This preserves every
122
- // internal boundary while leaving the final row without a bottom edge.
123
- borderBlockStart: `1px solid ${separator}`,
124
- outline: state.isFocusVisible ? `1px solid ${theme.focusColor}` : "none",
125
- outlineOffset: -1,
126
- background: "transparent",
127
- color: "inherit",
89
+ // Separators belong to the row that follows them, so the last row has no bottom edge.
90
+ borderBlockStart: `1px solid ${separatorColor(metrics)}`,
91
+ outline: `3px solid ${state.isFocusVisible ? colorOpacity(metrics.visual.colors.primary, 0.34) : "transparent"}`,
92
+ outlineOffset: -3,
128
93
  textAlign: "start",
129
94
  ...style
130
95
  }) });
131
96
  });
132
- function useTableContext() {
133
- const value = useContext(TableThemeContext);
134
- if (value == null)
97
+ function useTableStyle() {
98
+ const context = useContext(TableStyleContext);
99
+ if (context == null)
135
100
  throw new Error("Table parts must be used inside Table");
136
- return value;
137
- }
138
- function useTableTheme() {
139
- return useTableContext().theme;
101
+ return context;
140
102
  }
141
103
  /** Structured rows and columns with optional selection and consumer-owned sorting. */
142
104
  export const Table = Object.assign(TableRoot, {
143
- Header: TableHeader,
105
+ Header: TableHeaderImplementation,
144
106
  Column: TableColumn,
145
- Body: TableBody,
146
- Row: TableRow,
107
+ Body: TableBodyImplementation,
108
+ Row: TableRowImplementation,
147
109
  Cell: TableCell
148
110
  });