@stapel/listings-react 0.18.1 → 0.18.2
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/CHANGELOG.md +20 -0
- package/dist/default/ListingSerpCard.d.ts +9 -0
- package/dist/default/ListingSerpCard.d.ts.map +1 -1
- package/dist/default/ListingSerpCard.js +3 -1
- package/dist/default/ListingSerpCard.js.map +1 -1
- package/dist/default/favorite.d.ts +17 -0
- package/dist/default/favorite.d.ts.map +1 -1
- package/dist/default/favorite.js +11 -1
- package/dist/default/favorite.js.map +1 -1
- package/llms.txt +1 -1
- package/manifest.json +1 -1
- package/nav-manifest.json +1 -1
- package/package.json +2 -2
- package/src/analytics/generated/events.json +1 -1
- package/src/default/ListingSerpCard.tsx +13 -0
- package/src/default/favorite.tsx +52 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @stapel/listings-react
|
|
2
2
|
|
|
3
|
+
## 0.18.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The phone SERP card can state a blocked heart's refusal as a gesture (D45).
|
|
8
|
+
|
|
9
|
+
`<ListingSerpCard blockedReason="popover">` — the choice the grid card got in
|
|
10
|
+
the desktop pass, now on the one-column card a phone actually uses. A
|
|
11
|
+
signed-out walker measured "sign in to do this" printed fourteen times down
|
|
12
|
+
one search page, once under every card, in the line where a price or a place
|
|
13
|
+
belongs; fourteen copies of one sentence is not fourteen pieces of help. The
|
|
14
|
+
mechanism is this package's existing `GateReasonPopover`: it opens on tap as
|
|
15
|
+
readily as on hover, its anchor is `aria-disabled` rather than html-disabled
|
|
16
|
+
(so the tap is not swallowed), and the sentence stays in the accessibility
|
|
17
|
+
tree wired to the heart. `signIn` puts the door inside the disclosure.
|
|
18
|
+
|
|
19
|
+
Default unchanged — a host that says nothing still gets the standing line.
|
|
20
|
+
`<FavoriteHeart>` carries both arms now, so any surface mounting it inherits
|
|
21
|
+
the choice.
|
|
22
|
+
|
|
3
23
|
## 0.18.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
* the honest half; the missing data is written down rather than faked.
|
|
75
75
|
*/
|
|
76
76
|
import type { ReactElement, ReactNode } from "react";
|
|
77
|
+
import type { SignInCta } from "@stapel/core";
|
|
77
78
|
import type { ListingCard as ListingCardData } from "../api/types.js";
|
|
78
79
|
import type { ListingCardOpenProps } from "./ListingCard.js";
|
|
79
80
|
import type { CategoryFeaturesProp, ThemeModeProp } from "./types.js";
|
|
@@ -119,6 +120,14 @@ export interface ListingSerpCardBaseProps extends ThemeModeProp, CategoryFeature
|
|
|
119
120
|
/** Hide the favourite entirely — for a surface where it makes no sense (the
|
|
120
121
|
* owner's own listings). NOT a way to hide it from visitors. */
|
|
121
122
|
readonly showFavorite?: boolean;
|
|
123
|
+
/** How loudly the blocked heart states its refusal (D45) — `"text"`
|
|
124
|
+
* (default) keeps the standing sentence, `"popover"` moves it onto the
|
|
125
|
+
* gesture. A phone SERP is the surface that needed the choice: fourteen
|
|
126
|
+
* cards printed fourteen copies of one sentence. */
|
|
127
|
+
readonly blockedReason?: "text" | "popover";
|
|
128
|
+
/** The container's sign-in door, shown inside the disclosure when the
|
|
129
|
+
* refusal is a `"popover"`. */
|
|
130
|
+
readonly signIn?: SignInCta;
|
|
122
131
|
}
|
|
123
132
|
export type ListingSerpCardProps = ListingSerpCardBaseProps & ListingCardOpenProps;
|
|
124
133
|
export declare function ListingSerpCard(props: ListingSerpCardProps): ReactElement;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListingSerpCard.d.ts","sourceRoot":"","sources":["../../src/default/ListingSerpCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AACH,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ListingSerpCard.d.ts","sourceRoot":"","sources":["../../src/default/ListingSerpCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AACH,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAoBtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,wBACf,SAAQ,aAAa,EACnB,oBAAoB;IACtB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAC;IACxC;0CACsC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC;IAChC;gEAC4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IACjC;wDACoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B;oEACgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC;;;wDAGoD;IACpD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;mCAC+B;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,GAAG,oBAAoB,CAAC;AAUnF,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,CAwMzE"}
|
|
@@ -40,7 +40,9 @@ export function ListingSerpCard(props) {
|
|
|
40
40
|
const targetLabel = title.length > 0 ? title : t(LISTINGS_I18N_KEYS.cardUntitled);
|
|
41
41
|
const photos = listing.images ?? [];
|
|
42
42
|
const currency = listing.currency !== undefined ? { currency: listing.currency } : {};
|
|
43
|
-
const rail = props.actionsRail !== undefined || props.showFavorite !== false ? (_jsxs(Flex, { vertical: true, align: "flex-end", gap: spacing[2], style: RAIL, "data-testid": "listings-serp-actions", children: [props.actionsRail, props.showFavorite === false ? null : (_jsx(FavoriteHeart, { listingId: listing.id, favorited: listing.is_favorited, testId: "listings-serp-favorite"
|
|
43
|
+
const rail = props.actionsRail !== undefined || props.showFavorite !== false ? (_jsxs(Flex, { vertical: true, align: "flex-end", gap: spacing[2], style: RAIL, "data-testid": "listings-serp-actions", children: [props.actionsRail, props.showFavorite === false ? null : (_jsx(FavoriteHeart, { listingId: listing.id, favorited: listing.is_favorited, testId: "listings-serp-favorite", ...(props.blockedReason !== undefined
|
|
44
|
+
? { blockedReason: props.blockedReason }
|
|
45
|
+
: {}), ...(props.signIn !== undefined ? { signIn: props.signIn } : {}) }))] })) : null;
|
|
44
46
|
return (_jsxs(SkinTheme, { surface: "bare", ...(props.mode !== undefined ? { mode: props.mode } : {}), children: [_jsx("style", { href: CARD_TARGET_STYLE_HREF, precedence: "default", children: cardTargetCss() }), _jsx(Card, { size: "small", "data-testid": "listings-serp-card", "data-listing-id": listing.id, ...(status !== undefined
|
|
45
47
|
? { "data-listing-status": status.status }
|
|
46
48
|
: {}), styles: { body: { minWidth: 0, padding: token.paddingSM } }, style: { ["--listing-card-focus"]: token.colorPrimary }, children: _jsx("div", { className: CARD_QUERY_CLASS, children: _jsxs("div", { className: CARD_FRAME_CLASS, style: { gap: spacing[3] }, children: [_jsx("div", { className: CARD_MEDIA_CLASS, children: _jsx(ListingPhotoStrip, { images: photos, title: title.length > 0 ? title : String(listing.id), testId: "listings-serp-photos" }) }), _jsx("div", { className: CARD_MAIN_CLASS, children: _jsxs(Flex, { gap: spacing[3], align: "flex-start", children: [_jsxs(Flex, { vertical: true, gap: spacing[1], style: BODY, children: [_jsxs(CardTarget, { ...openProps(props), listingId: listing.id, label: targetLabel, testId: "listings-serp-open", bodyTestId: "listings-serp-body", children: [props.badge, _jsxs(Flex, { align: "center", gap: spacing[2], wrap: true, children: [_jsx(Typography.Text, { strong: true, style: { fontSize: fontSize.xl.fontSize }, "data-testid": "listings-serp-price", children: _jsx(ListingPrice, { amount: listing.price, ...currency }) }), priceTrend !== undefined && (_jsx(PriceTrendIcon, { direction: priceTrend.direction, label: t(priceTrend.direction === "down"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListingSerpCard.js","sourceRoot":"","sources":["../../src/default/ListingSerpCard.tsx"],"names":[],"mappings":";AA4EA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"ListingSerpCard.js","sourceRoot":"","sources":["../../src/default/ListingSerpCard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* list or a grid of these cards should wrap it in one.
|
|
27
27
|
*/
|
|
28
28
|
import type { CSSProperties, ReactElement } from "react";
|
|
29
|
+
import type { SignInCta } from "@stapel/core";
|
|
29
30
|
export interface FavoriteHeartProps {
|
|
30
31
|
readonly listingId: number;
|
|
31
32
|
/** `is_favorited` off the row. `null` is the third state — a search hit
|
|
@@ -36,6 +37,22 @@ export interface FavoriteHeartProps {
|
|
|
36
37
|
/** `"inline"` puts the reason beside the heart, `"stack"` (default) under
|
|
37
38
|
* it — the choice belongs to the surface, which knows its own geometry. */
|
|
38
39
|
readonly layout?: "stack" | "inline";
|
|
40
|
+
/**
|
|
41
|
+
* How loudly the refusal is stated on THIS surface (D45).
|
|
42
|
+
*
|
|
43
|
+
* `"text"` (default) keeps the standing sentence beside the heart — what
|
|
44
|
+
* this control has always done, and the right answer where one card is the
|
|
45
|
+
* screen. `"popover"` moves it onto the gesture: a signed-out phone SERP
|
|
46
|
+
* printed "sign in to do this" once under every one of fourteen cards, in
|
|
47
|
+
* the line where a price belongs, and fourteen copies of one sentence is
|
|
48
|
+
* not fourteen pieces of help. The reason never leaves the accessibility
|
|
49
|
+
* tree either way — see {@link GateReasonPopover}.
|
|
50
|
+
*/
|
|
51
|
+
readonly blockedReason?: "text" | "popover";
|
|
52
|
+
/** The surface's sign-in door, rendered INSIDE the disclosure. Absent: the
|
|
53
|
+
* disclosure holds the reason alone. Only read in the `"popover"` arm; the
|
|
54
|
+
* standing arm's door is the container's, as it always was. */
|
|
55
|
+
readonly signIn?: SignInCta;
|
|
39
56
|
readonly style?: CSSProperties;
|
|
40
57
|
}
|
|
41
58
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"favorite.d.ts","sourceRoot":"","sources":["../../src/default/favorite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"favorite.d.ts","sourceRoot":"","sources":["../../src/default/favorite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAGzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAO9C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;qEACiE;IACjE,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;+EAC2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;mEAE+D;IAC/D,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,YAAY,CAoErE"}
|
package/dist/default/favorite.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { SkinButton as Button } from "@stapel/tokens-antd/skin";
|
|
3
3
|
import { GatedControl } from "@stapel/tokens-antd/skin";
|
|
4
|
-
import { useT } from "@stapel/core";
|
|
4
|
+
import { useActionGate, useT } from "@stapel/core";
|
|
5
5
|
import { useFavoriteToggle } from "../headless/Favorites.js";
|
|
6
6
|
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
7
|
+
import { GateReasonPopover } from "./GateReasonPopover.js";
|
|
7
8
|
import { HeartIcon } from "./icons.js";
|
|
8
9
|
/**
|
|
9
10
|
* The heart and its refusal. It is NEVER hidden from a visitor: it is blocked,
|
|
@@ -18,6 +19,15 @@ export function FavoriteHeart(props) {
|
|
|
18
19
|
const label = t(favorite.favorited
|
|
19
20
|
? LISTINGS_I18N_KEYS.cardFavoriteRemove
|
|
20
21
|
: LISTINGS_I18N_KEYS.cardFavoriteAdd);
|
|
22
|
+
// The RESOLVED sentence, not the gate's key — `useActionGate` is the one
|
|
23
|
+
// place a blocked reason becomes words in this fleet.
|
|
24
|
+
const reason = useActionGate(favorite.gate).reason;
|
|
25
|
+
if (props.blockedReason === "popover" && reason !== undefined) {
|
|
26
|
+
// `aria-disabled`, never `disabled`: the gate already refuses the action
|
|
27
|
+
// (`toggle` is a no-op while blocked), and an html-disabled button
|
|
28
|
+
// swallows the hover, the focus and the tap the disclosure opens on.
|
|
29
|
+
return (_jsx(GateReasonPopover, { reason: reason, cta: props.signIn, testId: `${props.testId}-reason`, signInTestId: `${props.testId}-sign-in`, children: (bind) => (_jsx(Button, { shape: "circle", "aria-disabled": true, ...bind, "aria-label": label, "aria-pressed": favorite.favorited, "data-testid": props.testId, "data-favorited": String(favorite.favorited), "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", onClick: favorite.toggle, icon: _jsx(HeartIcon, { filled: favorite.favorited }), ...(props.style !== undefined ? { style: props.style } : {}) })) }));
|
|
30
|
+
}
|
|
21
31
|
return (_jsx(GatedControl, { gate: favorite.gate, testId: `${props.testId}-gate`, ...(props.layout !== undefined ? { layout: props.layout } : {}), ...(props.style !== undefined ? { style: props.style } : {}), children: (bind) => (_jsx(Button, { shape: "circle", disabled: bind.disabled, "data-disabled-reason": "the enclosing <GatedControl> renders the gate's reason beside this button", ...(bind["aria-describedby"] !== undefined
|
|
22
32
|
? { "aria-describedby": bind["aria-describedby"] }
|
|
23
33
|
: {}), "aria-label": label, "aria-pressed": favorite.favorited, "data-testid": props.testId, "data-favorited": String(favorite.favorited), "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", onClick: favorite.toggle, icon: _jsx(HeartIcon, { filled: favorite.favorited }) })) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"favorite.js","sourceRoot":"","sources":["../../src/default/favorite.tsx"],"names":[],"mappings":";AA4BA,OAAO,EAAE,UAAU,IAAI,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"favorite.js","sourceRoot":"","sources":["../../src/default/favorite.tsx"],"names":[],"mappings":"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"}
|
package/llms.txt
CHANGED
package/manifest.json
CHANGED
package/nav-manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stapel/listings-react",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"description": "Headless React pair for stapel-listings: a typed client over the draft twin, the composer that turns a category schema into a submittable listing, and the owner's dashboard. Two independent axes are rendered as two — lifecycle `status` decides visibility and `moderation_status` decides nothing about it, so an edit to a live listing stays live and says 'under review' instead of vanishing. Feature values are drawn and mirrored by @stapel/attributes-react; photos arrive as an injected upload bag whose `refs` ARE `images_draft` and whose `settled` gates the submit; a publish refusal is routed onto the control that caused it by slug. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -103,8 +103,8 @@
|
|
|
103
103
|
"typescript": "^5.8.3",
|
|
104
104
|
"vitest": "^3.2.4",
|
|
105
105
|
"@stapel/attributes-react": "^0.12.1",
|
|
106
|
-
"@stapel/core": "^0.23.1",
|
|
107
106
|
"@stapel/currencies-react": "^0.3.0",
|
|
107
|
+
"@stapel/core": "^0.23.1",
|
|
108
108
|
"@stapel/image": "^0.4.2",
|
|
109
109
|
"@stapel/showcase": "^0.3.0",
|
|
110
110
|
"@stapel/tokens": "^0.7.0",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
import type { CSSProperties, ReactElement, ReactNode } from "react";
|
|
77
77
|
import { Card, Flex, Typography, theme as antdTheme } from "antd";
|
|
78
78
|
import { SkinTheme } from "@stapel/tokens-antd/skin";
|
|
79
|
+
import type { SignInCta } from "@stapel/core";
|
|
79
80
|
import { useT } from "@stapel/core";
|
|
80
81
|
import { fontSize, spacing } from "@stapel/tokens";
|
|
81
82
|
import { FeatureBadges } from "@stapel/attributes-react/default";
|
|
@@ -149,6 +150,14 @@ export interface ListingSerpCardBaseProps
|
|
|
149
150
|
/** Hide the favourite entirely — for a surface where it makes no sense (the
|
|
150
151
|
* owner's own listings). NOT a way to hide it from visitors. */
|
|
151
152
|
readonly showFavorite?: boolean;
|
|
153
|
+
/** How loudly the blocked heart states its refusal (D45) — `"text"`
|
|
154
|
+
* (default) keeps the standing sentence, `"popover"` moves it onto the
|
|
155
|
+
* gesture. A phone SERP is the surface that needed the choice: fourteen
|
|
156
|
+
* cards printed fourteen copies of one sentence. */
|
|
157
|
+
readonly blockedReason?: "text" | "popover";
|
|
158
|
+
/** The container's sign-in door, shown inside the disclosure when the
|
|
159
|
+
* refusal is a `"popover"`. */
|
|
160
|
+
readonly signIn?: SignInCta;
|
|
152
161
|
}
|
|
153
162
|
|
|
154
163
|
export type ListingSerpCardProps = ListingSerpCardBaseProps & ListingCardOpenProps;
|
|
@@ -207,6 +216,10 @@ export function ListingSerpCard(props: ListingSerpCardProps): ReactElement {
|
|
|
207
216
|
listingId={listing.id}
|
|
208
217
|
favorited={listing.is_favorited}
|
|
209
218
|
testId="listings-serp-favorite"
|
|
219
|
+
{...(props.blockedReason !== undefined
|
|
220
|
+
? { blockedReason: props.blockedReason }
|
|
221
|
+
: {})}
|
|
222
|
+
{...(props.signIn !== undefined ? { signIn: props.signIn } : {})}
|
|
210
223
|
/>
|
|
211
224
|
)}
|
|
212
225
|
</Flex>
|
package/src/default/favorite.tsx
CHANGED
|
@@ -28,9 +28,11 @@
|
|
|
28
28
|
import type { CSSProperties, ReactElement } from "react";
|
|
29
29
|
import { SkinButton as Button } from "@stapel/tokens-antd/skin";
|
|
30
30
|
import { GatedControl } from "@stapel/tokens-antd/skin";
|
|
31
|
-
import {
|
|
31
|
+
import type { SignInCta } from "@stapel/core";
|
|
32
|
+
import { useActionGate, useT } from "@stapel/core";
|
|
32
33
|
import { useFavoriteToggle } from "../headless/Favorites.js";
|
|
33
34
|
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
35
|
+
import { GateReasonPopover } from "./GateReasonPopover.js";
|
|
34
36
|
import { HeartIcon } from "./icons.js";
|
|
35
37
|
|
|
36
38
|
export interface FavoriteHeartProps {
|
|
@@ -43,6 +45,22 @@ export interface FavoriteHeartProps {
|
|
|
43
45
|
/** `"inline"` puts the reason beside the heart, `"stack"` (default) under
|
|
44
46
|
* it — the choice belongs to the surface, which knows its own geometry. */
|
|
45
47
|
readonly layout?: "stack" | "inline";
|
|
48
|
+
/**
|
|
49
|
+
* How loudly the refusal is stated on THIS surface (D45).
|
|
50
|
+
*
|
|
51
|
+
* `"text"` (default) keeps the standing sentence beside the heart — what
|
|
52
|
+
* this control has always done, and the right answer where one card is the
|
|
53
|
+
* screen. `"popover"` moves it onto the gesture: a signed-out phone SERP
|
|
54
|
+
* printed "sign in to do this" once under every one of fourteen cards, in
|
|
55
|
+
* the line where a price belongs, and fourteen copies of one sentence is
|
|
56
|
+
* not fourteen pieces of help. The reason never leaves the accessibility
|
|
57
|
+
* tree either way — see {@link GateReasonPopover}.
|
|
58
|
+
*/
|
|
59
|
+
readonly blockedReason?: "text" | "popover";
|
|
60
|
+
/** The surface's sign-in door, rendered INSIDE the disclosure. Absent: the
|
|
61
|
+
* disclosure holds the reason alone. Only read in the `"popover"` arm; the
|
|
62
|
+
* standing arm's door is the container's, as it always was. */
|
|
63
|
+
readonly signIn?: SignInCta;
|
|
46
64
|
readonly style?: CSSProperties;
|
|
47
65
|
}
|
|
48
66
|
|
|
@@ -61,6 +79,39 @@ export function FavoriteHeart(props: FavoriteHeartProps): ReactElement {
|
|
|
61
79
|
? LISTINGS_I18N_KEYS.cardFavoriteRemove
|
|
62
80
|
: LISTINGS_I18N_KEYS.cardFavoriteAdd
|
|
63
81
|
);
|
|
82
|
+
// The RESOLVED sentence, not the gate's key — `useActionGate` is the one
|
|
83
|
+
// place a blocked reason becomes words in this fleet.
|
|
84
|
+
const reason = useActionGate(favorite.gate).reason;
|
|
85
|
+
if (props.blockedReason === "popover" && reason !== undefined) {
|
|
86
|
+
// `aria-disabled`, never `disabled`: the gate already refuses the action
|
|
87
|
+
// (`toggle` is a no-op while blocked), and an html-disabled button
|
|
88
|
+
// swallows the hover, the focus and the tap the disclosure opens on.
|
|
89
|
+
return (
|
|
90
|
+
<GateReasonPopover
|
|
91
|
+
reason={reason}
|
|
92
|
+
cta={props.signIn}
|
|
93
|
+
testId={`${props.testId}-reason`}
|
|
94
|
+
signInTestId={`${props.testId}-sign-in`}
|
|
95
|
+
>
|
|
96
|
+
{(bind) => (
|
|
97
|
+
<Button
|
|
98
|
+
shape="circle"
|
|
99
|
+
aria-disabled
|
|
100
|
+
{...bind}
|
|
101
|
+
aria-label={label}
|
|
102
|
+
aria-pressed={favorite.favorited}
|
|
103
|
+
data-testid={props.testId}
|
|
104
|
+
data-favorited={String(favorite.favorited)}
|
|
105
|
+
data-analytics="none"
|
|
106
|
+
data-analytics-reason="business action — host app wraps with its own tracked()"
|
|
107
|
+
onClick={favorite.toggle}
|
|
108
|
+
icon={<HeartIcon filled={favorite.favorited} />}
|
|
109
|
+
{...(props.style !== undefined ? { style: props.style } : {})}
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
112
|
+
</GateReasonPopover>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
64
115
|
return (
|
|
65
116
|
<GatedControl
|
|
66
117
|
gate={favorite.gate}
|