@multiplatform.one/forms 7.80.0 → 7.81.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/cjs/AdaptivePopup/index.cjs +2 -1
- package/dist/cjs/AdaptivePopup/index.native.js +2 -1
- package/dist/cjs/AdaptivePopup/index.native.js.map +1 -1
- package/dist/cjs/Button/index.cjs +12 -5
- package/dist/cjs/Button/index.native.js +14 -6
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/PanelHeader.cjs +3 -8
- package/dist/cjs/FloatingPanel/PanelHeader.native.js +3 -8
- package/dist/cjs/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/coverRect.cjs +0 -8
- package/dist/cjs/FloatingPanel/coverRect.native.js +0 -8
- package/dist/cjs/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/echoContext.cjs +5 -27
- package/dist/cjs/FloatingPanel/echoContext.native.js +5 -28
- package/dist/cjs/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/index.cjs +49 -131
- package/dist/cjs/FloatingPanel/index.native.js +216 -135
- package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
- package/dist/cjs/fields/Combobox/index.cjs +3 -10
- package/dist/cjs/fields/Combobox/index.native.js +3 -10
- package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/InlineChoices.cjs +4 -8
- package/dist/cjs/fields/Select/InlineChoices.native.js +4 -8
- package/dist/cjs/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/cjs/fields/Select/index.cjs +65 -201
- package/dist/cjs/fields/Select/index.native.js +74 -209
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +2 -2
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +2 -2
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.native.js +4 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/shared/CheckGlyph.cjs +40 -0
- package/dist/cjs/shared/CheckGlyph.native.js +44 -0
- package/dist/cjs/shared/CheckGlyph.native.js.map +1 -0
- package/dist/cjs/shared/checkedOnField.cjs +104 -0
- package/dist/cjs/shared/checkedOnField.native.js +117 -0
- package/dist/cjs/shared/checkedOnField.native.js.map +1 -0
- package/dist/cjs/shared/floatingList.cjs +12 -5
- package/dist/cjs/shared/floatingList.native.js +15 -7
- package/dist/cjs/shared/floatingList.native.js.map +1 -1
- package/dist/esm/AdaptivePopup/index.mjs +3 -2
- package/dist/esm/AdaptivePopup/index.mjs.map +1 -1
- package/dist/esm/AdaptivePopup/index.native.js +3 -2
- package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
- package/dist/esm/Button/index.mjs +12 -5
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +14 -6
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/FloatingPanel/PanelHeader.mjs +3 -8
- package/dist/esm/FloatingPanel/PanelHeader.mjs.map +1 -1
- package/dist/esm/FloatingPanel/PanelHeader.native.js +3 -8
- package/dist/esm/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/esm/FloatingPanel/coverRect.mjs +1 -8
- package/dist/esm/FloatingPanel/coverRect.mjs.map +1 -1
- package/dist/esm/FloatingPanel/coverRect.native.js +1 -8
- package/dist/esm/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/esm/FloatingPanel/echoContext.mjs +4 -24
- package/dist/esm/FloatingPanel/echoContext.mjs.map +1 -1
- package/dist/esm/FloatingPanel/echoContext.native.js +4 -25
- package/dist/esm/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/esm/FloatingPanel/index.mjs +53 -135
- package/dist/esm/FloatingPanel/index.mjs.map +1 -1
- package/dist/esm/FloatingPanel/index.native.js +180 -99
- package/dist/esm/FloatingPanel/index.native.js.map +1 -1
- package/dist/esm/fields/Combobox/index.mjs +4 -11
- package/dist/esm/fields/Combobox/index.mjs.map +1 -1
- package/dist/esm/fields/Combobox/index.native.js +4 -11
- package/dist/esm/fields/Combobox/index.native.js.map +1 -1
- package/dist/esm/fields/Select/InlineChoices.mjs +2 -6
- package/dist/esm/fields/Select/InlineChoices.mjs.map +1 -1
- package/dist/esm/fields/Select/InlineChoices.native.js +2 -6
- package/dist/esm/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/esm/fields/Select/index.mjs +68 -204
- package/dist/esm/fields/Select/index.mjs.map +1 -1
- package/dist/esm/fields/Select/index.native.js +77 -212
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +3 -3
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +3 -3
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/esm/index.mjs +3 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +3 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/shared/CheckGlyph.mjs +15 -0
- package/dist/esm/shared/CheckGlyph.mjs.map +1 -0
- package/dist/esm/shared/CheckGlyph.native.js +16 -0
- package/dist/esm/shared/CheckGlyph.native.js.map +1 -0
- package/dist/esm/shared/checkedOnField.mjs +76 -0
- package/dist/esm/shared/checkedOnField.mjs.map +1 -0
- package/dist/esm/shared/checkedOnField.native.js +86 -0
- package/dist/esm/shared/checkedOnField.native.js.map +1 -0
- package/dist/esm/shared/floatingList.mjs +9 -5
- package/dist/esm/shared/floatingList.mjs.map +1 -1
- package/dist/esm/shared/floatingList.native.js +12 -7
- package/dist/esm/shared/floatingList.native.js.map +1 -1
- package/dist/jsx/AdaptivePopup/index.mjs +3 -2
- package/dist/jsx/AdaptivePopup/index.mjs.map +1 -1
- package/dist/jsx/AdaptivePopup/index.native.js +2 -1
- package/dist/jsx/AdaptivePopup/index.native.js.map +1 -1
- package/dist/jsx/Button/index.mjs +12 -5
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +14 -6
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/PanelHeader.mjs +3 -8
- package/dist/jsx/FloatingPanel/PanelHeader.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/PanelHeader.native.js +3 -8
- package/dist/jsx/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/coverRect.mjs +1 -8
- package/dist/jsx/FloatingPanel/coverRect.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/coverRect.native.js +0 -8
- package/dist/jsx/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/echoContext.mjs +4 -24
- package/dist/jsx/FloatingPanel/echoContext.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/echoContext.native.js +5 -28
- package/dist/jsx/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/index.mjs +53 -135
- package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/index.native.js +216 -135
- package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
- package/dist/jsx/fields/Combobox/index.mjs +4 -11
- package/dist/jsx/fields/Combobox/index.mjs.map +1 -1
- package/dist/jsx/fields/Combobox/index.native.js +3 -10
- package/dist/jsx/fields/Combobox/index.native.js.map +1 -1
- package/dist/jsx/fields/Select/InlineChoices.mjs +2 -6
- package/dist/jsx/fields/Select/InlineChoices.mjs.map +1 -1
- package/dist/jsx/fields/Select/InlineChoices.native.js +4 -8
- package/dist/jsx/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/jsx/fields/Select/index.mjs +68 -204
- package/dist/jsx/fields/Select/index.mjs.map +1 -1
- package/dist/jsx/fields/Select/index.native.js +74 -209
- package/dist/jsx/fields/Select/index.native.js.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +3 -3
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +2 -2
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/jsx/index.js +3 -1
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +3 -1
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +4 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/shared/CheckGlyph.mjs +15 -0
- package/dist/jsx/shared/CheckGlyph.mjs.map +1 -0
- package/dist/jsx/shared/CheckGlyph.native.js +44 -0
- package/dist/jsx/shared/CheckGlyph.native.js.map +1 -0
- package/dist/jsx/shared/checkedOnField.mjs +76 -0
- package/dist/jsx/shared/checkedOnField.mjs.map +1 -0
- package/dist/jsx/shared/checkedOnField.native.js +117 -0
- package/dist/jsx/shared/checkedOnField.native.js.map +1 -0
- package/dist/jsx/shared/floatingList.mjs +9 -5
- package/dist/jsx/shared/floatingList.mjs.map +1 -1
- package/dist/jsx/shared/floatingList.native.js +15 -7
- package/dist/jsx/shared/floatingList.native.js.map +1 -1
- package/package.json +8 -8
- package/src/AdaptivePopup/index.tsx +6 -3
- package/src/Button/Button.iconButton.spec.tsx +39 -0
- package/src/Button/index.tsx +20 -8
- package/src/FloatingPanel/FloatingPanel.iconCover.spec.tsx +279 -0
- package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +78 -69
- package/src/FloatingPanel/FloatingPanel.seamlessCover.spec.tsx +12 -0
- package/src/FloatingPanel/PanelHeader.tsx +4 -12
- package/src/FloatingPanel/coverRect.ts +2 -15
- package/src/FloatingPanel/echoContext.ts +6 -40
- package/src/FloatingPanel/index.native.tsx +209 -144
- package/src/FloatingPanel/index.tsx +71 -187
- package/src/FloatingPanel/pointAnchor.spec.ts +1 -1
- package/src/FloatingPanel/pointAnchor.ts +4 -4
- package/src/FloatingPanel/sheetGrabber.spec.tsx +53 -0
- package/src/fields/Combobox/index.tsx +4 -9
- package/src/fields/Select/InlineChoices.tsx +2 -2
- package/src/fields/Select/Select.containingBlock.spec.tsx +131 -130
- package/src/fields/Select/Select.popUp.native.spec.tsx +276 -0
- package/src/fields/Select/Select.stories.tsx +26 -0
- package/src/fields/Select/index.tsx +84 -255
- package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +3 -3
- package/src/index.ts +2 -1
- package/src/shared/CheckGlyph.tsx +12 -0
- package/src/shared/checkedOnField.ts +116 -0
- package/src/shared/floatingList.spec.tsx +154 -1
- package/src/shared/floatingList.tsx +18 -9
- package/types/AdaptivePopup/index.d.ts.map +1 -1
- package/types/Button/index.d.ts.map +1 -1
- package/types/FloatingPanel/PanelHeader.d.ts +1 -6
- package/types/FloatingPanel/PanelHeader.d.ts.map +1 -1
- package/types/FloatingPanel/coverRect.d.ts +2 -11
- package/types/FloatingPanel/coverRect.d.ts.map +1 -1
- package/types/FloatingPanel/echoContext.d.ts +5 -22
- package/types/FloatingPanel/echoContext.d.ts.map +1 -1
- package/types/FloatingPanel/index.d.ts +22 -9
- package/types/FloatingPanel/index.d.ts.map +1 -1
- package/types/FloatingPanel/index.native.d.ts +8 -1
- package/types/FloatingPanel/index.native.d.ts.map +1 -1
- package/types/FloatingPanel/pointAnchor.d.ts +4 -4
- package/types/fields/Combobox/index.d.ts.map +1 -1
- package/types/fields/Select/index.d.ts.map +1 -1
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +1 -1
- package/types/shared/CheckGlyph.d.ts +10 -0
- package/types/shared/CheckGlyph.d.ts.map +1 -0
- package/types/shared/checkedOnField.d.ts +54 -0
- package/types/shared/checkedOnField.d.ts.map +1 -0
- package/types/shared/floatingList.d.ts +5 -2
- package/types/shared/floatingList.d.ts.map +1 -1
- package/dist/cjs/FloatingPanel/iconEcho.cjs +0 -106
- package/dist/cjs/FloatingPanel/iconEcho.native.js +0 -116
- package/dist/cjs/FloatingPanel/iconEcho.native.js.map +0 -1
- package/dist/esm/FloatingPanel/iconEcho.mjs +0 -79
- package/dist/esm/FloatingPanel/iconEcho.mjs.map +0 -1
- package/dist/esm/FloatingPanel/iconEcho.native.js +0 -86
- package/dist/esm/FloatingPanel/iconEcho.native.js.map +0 -1
- package/dist/jsx/FloatingPanel/iconEcho.mjs +0 -79
- package/dist/jsx/FloatingPanel/iconEcho.mjs.map +0 -1
- package/dist/jsx/FloatingPanel/iconEcho.native.js +0 -116
- package/dist/jsx/FloatingPanel/iconEcho.native.js.map +0 -1
- package/src/FloatingPanel/FloatingPanel.iconEcho.spec.tsx +0 -357
- package/src/FloatingPanel/iconEcho.tsx +0 -124
- package/types/FloatingPanel/iconEcho.d.ts +0 -29
- package/types/FloatingPanel/iconEcho.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/forms",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.81.1",
|
|
4
4
|
"description": "Cross-platform form components with URL state management",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cross-platform",
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@floating-ui/react": "^0.27.20",
|
|
60
|
-
"@multiplatform.one/platform": "^7.
|
|
61
|
-
"@multiplatform.one/router": "^7.
|
|
62
|
-
"@multiplatform.one/table-primitives": "^7.
|
|
63
|
-
"@multiplatform.one/theme": "^7.
|
|
60
|
+
"@multiplatform.one/platform": "^7.81.1",
|
|
61
|
+
"@multiplatform.one/router": "^7.81.1",
|
|
62
|
+
"@multiplatform.one/table-primitives": "^7.81.1",
|
|
63
|
+
"@multiplatform.one/theme": "^7.81.1",
|
|
64
64
|
"@phosphor-icons/react": "^2.1.10",
|
|
65
65
|
"@tamagui/get-font-sized": "2.7.6",
|
|
66
66
|
"@tamagui/get-token": "2.7.6",
|
|
@@ -80,9 +80,9 @@
|
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@expo/ui": "~55.0.17",
|
|
83
|
-
"@multiplatform.one/config": "7.
|
|
84
|
-
"@multiplatform.one/storybook": "7.
|
|
85
|
-
"@multiplatform.one/test-utils": "7.
|
|
83
|
+
"@multiplatform.one/config": "7.81.1",
|
|
84
|
+
"@multiplatform.one/storybook": "7.81.1",
|
|
85
|
+
"@multiplatform.one/test-utils": "7.81.1",
|
|
86
86
|
"@tamagui/build": "2.7.6",
|
|
87
87
|
"@tamagui/config": "2.7.6",
|
|
88
88
|
"@tamagui/vite-plugin": "2.7.6",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { transitionProps } from "@multiplatform.one/theme";
|
|
1
|
+
import { sheetGrabber, transitionProps, overlayScrim } from "@multiplatform.one/theme";
|
|
2
2
|
import { DialogContent, DialogOverlay } from "@multiplatform.one/theme";
|
|
3
3
|
import {
|
|
4
4
|
cloneElement,
|
|
@@ -399,12 +399,15 @@ export function AdaptivePopup({
|
|
|
399
399
|
{...transitionProps(knobProps.motion.slide)}
|
|
400
400
|
>
|
|
401
401
|
<Sheet.Overlay
|
|
402
|
-
backgroundColor=
|
|
402
|
+
backgroundColor={overlayScrim}
|
|
403
403
|
{...transitionProps(knobProps.motion.scrim)}
|
|
404
404
|
enterStyle={{ opacity: 0 }}
|
|
405
405
|
exitStyle={{ opacity: 0 }}
|
|
406
406
|
/>
|
|
407
|
-
<Sheet.Handle
|
|
407
|
+
<Sheet.Handle
|
|
408
|
+
{...sheetGrabber}
|
|
409
|
+
{...({ "aria-hidden": true } as Record<string, unknown>)}
|
|
410
|
+
/>
|
|
408
411
|
<Sheet.Frame
|
|
409
412
|
// Sheet.Frame forwards its ref at runtime; the styled prop type
|
|
410
413
|
// just omits `ref`, hence the cast. tabIndex -1 makes the frame a
|
|
@@ -112,6 +112,45 @@ function rest(el: Element, prefix: string): string[] {
|
|
|
112
112
|
.sort();
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
describe("LC-176: one ink on a bold glyph (FZ-8649)", () => {
|
|
116
|
+
function Inked({ color, stroke }: { color?: string; stroke?: string }) {
|
|
117
|
+
return <svg data-glyph="" data-color={color} data-stroke={stroke} />;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
it.each(SCHEMES)("%s: a bar glyph takes the bar tint on its fill and its stroke", (scheme) => {
|
|
121
|
+
houseRender(
|
|
122
|
+
<Surface bar>
|
|
123
|
+
<Button chromeless icon={Inked} aria-label="Menu" />
|
|
124
|
+
<Button chromeless icon={<Inked />} aria-label="Bell" />
|
|
125
|
+
</Surface>,
|
|
126
|
+
scheme,
|
|
127
|
+
);
|
|
128
|
+
for (const label of ["Menu", "Bell"]) {
|
|
129
|
+
const ink = glyphOf(label).getAttribute("data-color");
|
|
130
|
+
expect(ink).toBeTruthy();
|
|
131
|
+
expect(ink).not.toBe("currentColor");
|
|
132
|
+
expect(glyphOf(label).getAttribute("data-stroke")).toBe(ink);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("a caller's colour stays on the fill and the stroke", () => {
|
|
137
|
+
houseRender(
|
|
138
|
+
<Surface bar>
|
|
139
|
+
<Button chromeless icon={<Inked color="red" />} aria-label="Bell" />
|
|
140
|
+
</Surface>,
|
|
141
|
+
"light",
|
|
142
|
+
);
|
|
143
|
+
expect(glyphOf("Bell").getAttribute("data-color")).toBe("red");
|
|
144
|
+
expect(glyphOf("Bell").getAttribute("data-stroke")).toBe("red");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("a content glyph has no bar tint to pass, so it keeps currentColor", () => {
|
|
148
|
+
houseRender(<Button icon={Inked} aria-label="More" />, "light");
|
|
149
|
+
expect(glyphOf("More").hasAttribute("data-color")).toBe(false);
|
|
150
|
+
expect(glyphOf("More").getAttribute("data-stroke")).toBe("currentColor");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
115
154
|
describe("LC-176: an icon-only Button is a bare glyph", () => {
|
|
116
155
|
it.each(SCHEMES)("%s: in a bar, no fill and no border at rest, the bar tint as ink", (scheme) => {
|
|
117
156
|
houseRender(
|
package/src/Button/index.tsx
CHANGED
|
@@ -465,28 +465,40 @@ export function iconButtonGlyphPx(rowLinePx: number | undefined): number {
|
|
|
465
465
|
/** LC-176: an inline glyph is the text's size; Phosphor's 256 grid pads ~12%. */
|
|
466
466
|
const INLINE_GLYPH_RATIO = 1.2;
|
|
467
467
|
|
|
468
|
+
/**
|
|
469
|
+
* `ink` is the bar tint. It goes to the glyph as well as to its stroke, because
|
|
470
|
+
* a `themed` glyph fills with the theme's text colour unless told otherwise and
|
|
471
|
+
* the stroke would then outline it in a second colour (FZ-8649).
|
|
472
|
+
*/
|
|
468
473
|
function withGlyphWeight(
|
|
469
474
|
icon: IconLike | undefined,
|
|
470
475
|
size: number,
|
|
471
476
|
weight: "regular" | "bold",
|
|
472
477
|
floor = false,
|
|
478
|
+
ink?: string | null,
|
|
473
479
|
): IconLike | undefined {
|
|
474
480
|
if (icon == null || icon === false) return icon;
|
|
481
|
+
const tint = ink ?? undefined;
|
|
475
482
|
if (isValidElement<{ size?: number; weight?: string; color?: string }>(icon)) {
|
|
476
483
|
const resolved = (icon.props.weight ?? weight) as "regular" | "bold";
|
|
477
484
|
const own = icon.props.size;
|
|
478
485
|
return cloneElement(icon, {
|
|
479
486
|
...(own == null || (floor && typeof own === "number" && own < size) ? { size } : undefined),
|
|
480
487
|
...(icon.props.weight == null ? { weight } : undefined),
|
|
481
|
-
...
|
|
488
|
+
...(icon.props.color == null && tint ? { color: tint } : undefined),
|
|
489
|
+
...boldStroke(resolved, icon.props.color ?? tint),
|
|
482
490
|
});
|
|
483
491
|
}
|
|
484
492
|
if (typeof icon === "function" || (typeof icon === "object" && "$$typeof" in icon)) {
|
|
485
|
-
return createElement(
|
|
486
|
-
size
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
493
|
+
return createElement(
|
|
494
|
+
icon as ComponentType<{ size?: number; weight?: string; color?: string }>,
|
|
495
|
+
{
|
|
496
|
+
size,
|
|
497
|
+
weight,
|
|
498
|
+
...(tint ? { color: tint } : undefined),
|
|
499
|
+
...boldStroke(weight, tint),
|
|
500
|
+
},
|
|
501
|
+
);
|
|
490
502
|
}
|
|
491
503
|
return icon;
|
|
492
504
|
}
|
|
@@ -911,7 +923,7 @@ export function Button({
|
|
|
911
923
|
const barGlyphFloor = barGlyph && !framedCircle && !inlineGlyph && !nested;
|
|
912
924
|
const glyph =
|
|
913
925
|
barGlyph || inlineGlyph || framedCircle
|
|
914
|
-
? withGlyphWeight(icon, iconSize, resolvedGlyphWeight, barGlyphFloor)
|
|
926
|
+
? withGlyphWeight(icon, iconSize, resolvedGlyphWeight, barGlyphFloor, barInk)
|
|
915
927
|
: icon;
|
|
916
928
|
|
|
917
929
|
// LC-158: the frame keeps its recipe height under Dynamic Type, so the
|
|
@@ -998,7 +1010,7 @@ export function Button({
|
|
|
998
1010
|
) : null;
|
|
999
1011
|
const glyphAfter =
|
|
1000
1012
|
iconAfter && (barGlyph || inlineGlyph || framedCircle)
|
|
1001
|
-
? withGlyphWeight(iconAfter, iconSize, resolvedGlyphWeight, barGlyphFloor)
|
|
1013
|
+
? withGlyphWeight(iconAfter, iconSize, resolvedGlyphWeight, barGlyphFloor, barInk)
|
|
1002
1014
|
: iconAfter;
|
|
1003
1015
|
const iconAfterElement = glyphAfter ? (
|
|
1004
1016
|
<View flexShrink={0}>
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LC-112 icon trigger cover (FZ-8665 Round 45, reversing Round 37's X): a
|
|
3
|
+
* panel that covers the icon button it opened from sits exactly over the
|
|
4
|
+
* surface that button is seen on and repeats nothing. Its content starts at
|
|
5
|
+
* its top edge, a press outside closes it, and Escape closes it on the web.
|
|
6
|
+
*/
|
|
7
|
+
import { PlusIcon } from "@phosphor-icons/react";
|
|
8
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
9
|
+
import { fireEvent, waitFor, within } from "@testing-library/react";
|
|
10
|
+
import { useState } from "react";
|
|
11
|
+
import { MIN_PRESS_TARGET } from "@multiplatform.one/theme";
|
|
12
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
import { Text, XStack } from "tamagui";
|
|
14
|
+
import { Button } from "../Button";
|
|
15
|
+
import { FloatingPanel } from "./index";
|
|
16
|
+
import { PanelHeader } from "./PanelHeader";
|
|
17
|
+
|
|
18
|
+
type Rect = { x: number; y: number; width: number; height: number };
|
|
19
|
+
|
|
20
|
+
function measure(trigger: Rect, panelWidth = 240, panelHeight = 160, surface?: Rect) {
|
|
21
|
+
const original = HTMLElement.prototype.getBoundingClientRect;
|
|
22
|
+
const rect = {
|
|
23
|
+
...trigger,
|
|
24
|
+
top: trigger.y,
|
|
25
|
+
left: trigger.x,
|
|
26
|
+
right: trigger.x + trigger.width,
|
|
27
|
+
bottom: trigger.y + trigger.height,
|
|
28
|
+
toJSON: () => ({}),
|
|
29
|
+
};
|
|
30
|
+
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(
|
|
31
|
+
function (this: HTMLElement) {
|
|
32
|
+
if (this.hasAttribute("data-fp-reference")) return rect;
|
|
33
|
+
if (surface && this.hasAttribute("data-fp-cover-surface")) {
|
|
34
|
+
return {
|
|
35
|
+
...surface,
|
|
36
|
+
top: surface.y,
|
|
37
|
+
left: surface.x,
|
|
38
|
+
right: surface.x + surface.width,
|
|
39
|
+
bottom: surface.y + surface.height,
|
|
40
|
+
toJSON: () => ({}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return original.call(this);
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockImplementation(
|
|
47
|
+
function (this: HTMLElement) {
|
|
48
|
+
return this.hasAttribute("data-fp-panel") ? panelWidth : 0;
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
vi.spyOn(HTMLElement.prototype, "scrollHeight", "get").mockImplementation(
|
|
52
|
+
function (this: HTMLElement) {
|
|
53
|
+
return this.hasAttribute("data-fp-panel") ? panelHeight : 0;
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function AddPanel({ header = false }: { header?: boolean }) {
|
|
59
|
+
const [open, setOpen] = useState(false);
|
|
60
|
+
return (
|
|
61
|
+
<FloatingPanel
|
|
62
|
+
open={open}
|
|
63
|
+
onOpenChange={setOpen}
|
|
64
|
+
anchorMode="cover-trigger"
|
|
65
|
+
compactFace="menu"
|
|
66
|
+
triggerEcho={false}
|
|
67
|
+
iconCover
|
|
68
|
+
coverAlign="auto"
|
|
69
|
+
widthMode="fit-content"
|
|
70
|
+
triggerSizing="content"
|
|
71
|
+
contentPadding="none"
|
|
72
|
+
trigger={<Button icon={PlusIcon} aria-label="Add node" />}
|
|
73
|
+
>
|
|
74
|
+
{header ? (
|
|
75
|
+
<PanelHeader>
|
|
76
|
+
<Text>Add a node</Text>
|
|
77
|
+
</PanelHeader>
|
|
78
|
+
) : (
|
|
79
|
+
<Text>Add a node</Text>
|
|
80
|
+
)}
|
|
81
|
+
<Button>Copy</Button>
|
|
82
|
+
</FloatingPanel>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** An account menu opened from a 32pt avatar: a trigger with no `icon`. */
|
|
87
|
+
function AvatarPanel() {
|
|
88
|
+
return (
|
|
89
|
+
<FloatingPanel
|
|
90
|
+
open
|
|
91
|
+
onOpenChange={() => {}}
|
|
92
|
+
anchorMode="cover-trigger"
|
|
93
|
+
compactFace="menu"
|
|
94
|
+
triggerEcho={false}
|
|
95
|
+
iconCover
|
|
96
|
+
coverAlign="auto"
|
|
97
|
+
widthMode="fit-content"
|
|
98
|
+
triggerSizing="content"
|
|
99
|
+
contentPadding="none"
|
|
100
|
+
trigger={
|
|
101
|
+
<span role="button" aria-label="Account">
|
|
102
|
+
AD
|
|
103
|
+
</span>
|
|
104
|
+
}
|
|
105
|
+
>
|
|
106
|
+
<Text>Sign out</Text>
|
|
107
|
+
</FloatingPanel>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function open(result: ReturnType<typeof renderWithProviders>) {
|
|
112
|
+
fireEvent.click(result.getByRole("button", { name: "Add node" }), { detail: 0 });
|
|
113
|
+
const panel = (await result.findByTestId("floating-panel-viewport")) as HTMLElement;
|
|
114
|
+
await waitFor(() => expect(panel.style.visibility).toBe("visible"));
|
|
115
|
+
return panel;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
afterEach(() => {
|
|
119
|
+
vi.restoreAllMocks();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const buttonsIn = (panel: HTMLElement) =>
|
|
123
|
+
within(panel)
|
|
124
|
+
.getAllByRole("button")
|
|
125
|
+
.map((button) => button.textContent || button.getAttribute("aria-label"));
|
|
126
|
+
|
|
127
|
+
/** Whatever sits above the content in the panel: a hidden scroll arrow at most. */
|
|
128
|
+
const aboveContent = (panel: HTMLElement) => {
|
|
129
|
+
const children = [...panel.children] as HTMLElement[];
|
|
130
|
+
const content = children.findIndex((child) => child.textContent?.includes("Add a node"));
|
|
131
|
+
expect(content).toBeGreaterThanOrEqual(0);
|
|
132
|
+
return children.slice(0, content);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
describe("LC-112 icon trigger cover on the web (FZ-8665)", () => {
|
|
136
|
+
const viewportWidth = () => document.documentElement.clientWidth || window.innerWidth;
|
|
137
|
+
|
|
138
|
+
it("covers the + from the button's top trailing corner and repeats nothing", async () => {
|
|
139
|
+
const button = { x: viewportWidth() - 16 - 44, y: 12, width: 44, height: 44 };
|
|
140
|
+
measure(button, 240);
|
|
141
|
+
const result = renderWithProviders(<AddPanel />);
|
|
142
|
+
const panel = await open(result);
|
|
143
|
+
expect(panel.style.top).toBe(`${button.y}px`);
|
|
144
|
+
expect(panel.style.left).toBe(`${button.x + button.width - 240}px`);
|
|
145
|
+
expect(within(panel).queryByRole("button", { name: "Close" })).toBeNull();
|
|
146
|
+
expect(within(panel).queryByRole("button", { name: "Add node" })).toBeNull();
|
|
147
|
+
expect(panel.querySelector("[data-fp-trigger-row]")).toBeNull();
|
|
148
|
+
expect(panel.querySelector("[data-fp-icon-echo]")).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("starts the content at the panel's top edge, with no band above it", async () => {
|
|
152
|
+
measure({ x: 16, y: 12, width: 44, height: 44 });
|
|
153
|
+
const result = renderWithProviders(<AddPanel />);
|
|
154
|
+
const panel = await open(result);
|
|
155
|
+
for (const element of aboveContent(panel)) {
|
|
156
|
+
expect(element.textContent).toBe("");
|
|
157
|
+
expect(element.querySelector("button, [role='button']")).toBeNull();
|
|
158
|
+
expect(element.style.height).toBe("");
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("covers a grouped trigger's whole plate, with both edges, and shows none of its buttons", async () => {
|
|
163
|
+
const button = { x: viewportWidth() - 16 - 44, y: 12, width: 44, height: 44 };
|
|
164
|
+
const plate = { x: button.x - 100, y: 12, width: 144, height: 44 };
|
|
165
|
+
measure(button, 120, 160, plate);
|
|
166
|
+
const result = renderWithProviders(
|
|
167
|
+
<div data-fp-cover-surface="">
|
|
168
|
+
<Button icon={PlusIcon} aria-label="Search" />
|
|
169
|
+
<AddPanel />
|
|
170
|
+
</div>,
|
|
171
|
+
);
|
|
172
|
+
const panel = await open(result);
|
|
173
|
+
// The plate is wider than the menu, so the menu takes the plate's width
|
|
174
|
+
// and both edges.
|
|
175
|
+
expect(panel.style.left).toBe(`${plate.x}px`);
|
|
176
|
+
expect(panel.style.top).toBe(`${plate.y}px`);
|
|
177
|
+
expect(panel.style.minWidth).toBe(`${plate.width}px`);
|
|
178
|
+
expect(buttonsIn(panel)).toEqual(["Copy"]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("closes on a press outside the panel", async () => {
|
|
182
|
+
measure({ x: 16, y: 12, width: 44, height: 44 });
|
|
183
|
+
const result = renderWithProviders(<AddPanel />);
|
|
184
|
+
await open(result);
|
|
185
|
+
fireEvent.click(document.body);
|
|
186
|
+
await waitFor(() => expect(result.queryByTestId("floating-panel-viewport")).toBeNull());
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("closes on Escape and hands focus back to the button", async () => {
|
|
190
|
+
measure({ x: 16, y: 12, width: 44, height: 44 });
|
|
191
|
+
const result = renderWithProviders(<AddPanel />);
|
|
192
|
+
const trigger = result.getByRole("button", { name: "Add node" });
|
|
193
|
+
trigger.focus();
|
|
194
|
+
await open(result);
|
|
195
|
+
fireEvent.keyDown(document, { key: "Escape" });
|
|
196
|
+
await waitFor(() => expect(result.queryByTestId("floating-panel-viewport")).toBeNull());
|
|
197
|
+
await waitFor(() => expect(document.activeElement).toBe(trigger));
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("puts a panel's title on the panel's normal padding, keeping clear of nothing", async () => {
|
|
201
|
+
const button = { x: viewportWidth() - 16 - 44, y: 12, width: 44, height: 44 };
|
|
202
|
+
measure(button, 240);
|
|
203
|
+
const result = renderWithProviders(
|
|
204
|
+
<>
|
|
205
|
+
<AddPanel header />
|
|
206
|
+
<XStack
|
|
207
|
+
data-testid="normal-padding"
|
|
208
|
+
alignItems="center"
|
|
209
|
+
gap="$2"
|
|
210
|
+
paddingLeft="$4"
|
|
211
|
+
paddingRight="$4"
|
|
212
|
+
paddingTop="$4"
|
|
213
|
+
/>
|
|
214
|
+
</>,
|
|
215
|
+
);
|
|
216
|
+
const panel = await open(result);
|
|
217
|
+
const header = panel.querySelector<HTMLElement>("[data-fp-panel-header]") as HTMLElement;
|
|
218
|
+
expect(header.className).toBe(result.getByTestId("normal-padding").className);
|
|
219
|
+
expect(header.style.minHeight).toBe("");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("keeps a content-sized trigger on a centring row's line", async () => {
|
|
223
|
+
measure({ x: 16, y: 12, width: 44, height: 44 });
|
|
224
|
+
const centred = renderWithProviders(
|
|
225
|
+
<div style={{ display: "flex", flexDirection: "row", alignItems: "center", height: 60 }}>
|
|
226
|
+
<AddPanel />
|
|
227
|
+
</div>,
|
|
228
|
+
);
|
|
229
|
+
const inRow = centred.container.querySelector<HTMLElement>(
|
|
230
|
+
"[data-fp-reference]",
|
|
231
|
+
) as HTMLElement;
|
|
232
|
+
await waitFor(() => expect(getComputedStyle(inRow).alignSelf).not.toBe("flex-start"));
|
|
233
|
+
centred.unmount();
|
|
234
|
+
const column = renderWithProviders(
|
|
235
|
+
<div style={{ display: "flex", flexDirection: "column", alignItems: "stretch" }}>
|
|
236
|
+
<AddPanel />
|
|
237
|
+
</div>,
|
|
238
|
+
);
|
|
239
|
+
const inColumn = column.container.querySelector<HTMLElement>(
|
|
240
|
+
"[data-fp-reference]",
|
|
241
|
+
) as HTMLElement;
|
|
242
|
+
expect(getComputedStyle(inColumn).alignSelf).toBe("flex-start");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("gives a keyboard its content and no extra stop", async () => {
|
|
246
|
+
measure({ x: 16, y: 12, width: 44, height: 44 });
|
|
247
|
+
const result = renderWithProviders(<AddPanel />);
|
|
248
|
+
const panel = await open(result);
|
|
249
|
+
expect(buttonsIn(panel)).toEqual(["Copy"]);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("opens up from the button's bottom edge near the bottom, still covering it", async () => {
|
|
253
|
+
const button = { x: 16, y: window.innerHeight - 60, width: 44, height: 44 };
|
|
254
|
+
measure(button, 240, 300);
|
|
255
|
+
const result = renderWithProviders(<AddPanel />);
|
|
256
|
+
const panel = await open(result);
|
|
257
|
+
const frame = getComputedStyle(panel);
|
|
258
|
+
const borders =
|
|
259
|
+
(parseFloat(frame.borderTopWidth) || 0) + (parseFloat(frame.borderBottomWidth) || 0);
|
|
260
|
+
expect(parseFloat(panel.style.top) + 300 + borders).toBe(button.y + button.height);
|
|
261
|
+
expect(panel.style.left).toBe(`${button.x}px`);
|
|
262
|
+
expect(panel.style.transformOrigin).toBe("bottom left");
|
|
263
|
+
expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("covers a 32pt avatar at its 44pt target, its centre on the row-icon column (FZ-8480 GAP)", async () => {
|
|
267
|
+
const avatar = { x: 100, y: 18, width: 32, height: 32 };
|
|
268
|
+
measure(avatar, 240);
|
|
269
|
+
const result = renderWithProviders(<AvatarPanel />);
|
|
270
|
+
const panel = (await result.findByTestId("floating-panel-viewport")) as HTMLElement;
|
|
271
|
+
await waitFor(() => expect(panel.style.visibility).toBe("visible"));
|
|
272
|
+
const inset = (MIN_PRESS_TARGET - avatar.width) / 2;
|
|
273
|
+
expect(panel.style.left).toBe(`${avatar.x - inset}px`);
|
|
274
|
+
expect(panel.style.top).toBe(`${avatar.y - inset}px`);
|
|
275
|
+
// The avatar's centre sits half a press target in, the centre of a menu
|
|
276
|
+
// row's leading glyph slot.
|
|
277
|
+
expect(inset + avatar.width / 2).toBe(MIN_PRESS_TARGET / 2);
|
|
278
|
+
});
|
|
279
|
+
});
|