@stapel/listings-react 0.18.2 → 0.19.0
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 +41 -0
- package/dist/api/generated/schema.d.ts +101 -13
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/api/listingsApi.d.ts +26 -1
- package/dist/api/listingsApi.d.ts.map +1 -1
- package/dist/api/listingsApi.js +9 -0
- package/dist/api/listingsApi.js.map +1 -1
- package/dist/api/types.d.ts +91 -6
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js +32 -0
- package/dist/api/types.js.map +1 -1
- package/dist/default/GateReasonPopover.d.ts.map +1 -1
- package/dist/default/GateReasonPopover.js +47 -2
- package/dist/default/GateReasonPopover.js.map +1 -1
- package/dist/default/ListingCard.d.ts +32 -1
- package/dist/default/ListingCard.d.ts.map +1 -1
- package/dist/default/ListingCard.js +65 -14
- package/dist/default/ListingCard.js.map +1 -1
- package/dist/default/ListingDetailPane.d.ts.map +1 -1
- package/dist/default/ListingDetailPane.js +12 -7
- package/dist/default/ListingDetailPane.js.map +1 -1
- package/dist/default/ListingFeedCard.d.ts +32 -7
- package/dist/default/ListingFeedCard.d.ts.map +1 -1
- package/dist/default/ListingFeedCard.js +11 -3
- package/dist/default/ListingFeedCard.js.map +1 -1
- package/dist/default/ListingPrice.d.ts +13 -2
- package/dist/default/ListingPrice.d.ts.map +1 -1
- package/dist/default/ListingPrice.js.map +1 -1
- package/dist/default/ListingSerpCard.d.ts.map +1 -1
- package/dist/default/ListingSerpCard.js +10 -2
- package/dist/default/ListingSerpCard.js.map +1 -1
- package/dist/default/favorite.d.ts +11 -0
- package/dist/default/favorite.d.ts.map +1 -1
- package/dist/default/favorite.js +24 -9
- package/dist/default/favorite.js.map +1 -1
- package/dist/default/icons.d.ts +17 -3
- package/dist/default/icons.d.ts.map +1 -1
- package/dist/default/icons.js +2 -1
- package/dist/default/icons.js.map +1 -1
- package/dist/default/index.d.ts +1 -1
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +4 -1
- package/dist/default/index.js.map +1 -1
- package/dist/headless/Engagement.d.ts +62 -0
- package/dist/headless/Engagement.d.ts.map +1 -0
- package/dist/headless/Engagement.js +116 -0
- package/dist/headless/Engagement.js.map +1 -0
- package/dist/headless/Favorites.d.ts +50 -0
- package/dist/headless/Favorites.d.ts.map +1 -1
- package/dist/headless/Favorites.js +63 -3
- package/dist/headless/Favorites.js.map +1 -1
- package/dist/headless/ListingDetail.d.ts +21 -1
- package/dist/headless/ListingDetail.d.ts.map +1 -1
- package/dist/headless/ListingDetail.js +39 -3
- package/dist/headless/ListingDetail.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +4 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/generated/errors.es.gen.d.ts +1 -1
- package/dist/i18n/generated/errors.es.gen.js +1 -1
- package/dist/i18n/generated/errors.gen.d.ts +12 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.gen.js +6 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -1
- package/dist/i18n/generated/errors.ru.gen.d.ts +1 -1
- package/dist/i18n/generated/errors.ru.gen.js +1 -1
- package/dist/i18n/keys.d.ts +7 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +8 -0
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +6 -0
- package/dist/i18n/ru.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/model/engagement.d.ts +85 -0
- package/dist/model/engagement.d.ts.map +1 -0
- package/dist/model/engagement.js +59 -0
- package/dist/model/engagement.js.map +1 -0
- package/dist/model/queries.d.ts +28 -1
- package/dist/model/queries.d.ts.map +1 -1
- package/dist/model/queries.js +36 -0
- package/dist/model/queries.js.map +1 -1
- package/dist/model/queryKeys.d.ts +12 -13
- package/dist/model/queryKeys.d.ts.map +1 -1
- package/dist/model/queryKeys.js +15 -0
- package/dist/model/queryKeys.js.map +1 -1
- package/llms.txt +6 -3
- package/manifest.json +71 -3
- package/nav-manifest.json +1 -1
- package/package.json +8 -8
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +101 -13
- package/src/api/listingsApi.ts +39 -0
- package/src/api/types.ts +115 -6
- package/src/default/GateReasonPopover.tsx +62 -3
- package/src/default/ListingCard.tsx +84 -10
- package/src/default/ListingDetailPane.tsx +48 -16
- package/src/default/ListingFeedCard.tsx +45 -8
- package/src/default/ListingPrice.tsx +13 -2
- package/src/default/ListingSerpCard.tsx +11 -1
- package/src/default/favorite.tsx +49 -21
- package/src/default/icons.tsx +21 -3
- package/src/default/index.ts +4 -0
- package/src/headless/Engagement.tsx +177 -0
- package/src/headless/Favorites.tsx +93 -3
- package/src/headless/ListingDetail.tsx +64 -4
- package/src/i18n/es.ts +7 -0
- package/src/i18n/generated/errors.es.gen.ts +1 -1
- package/src/i18n/generated/errors.gen.ts +6 -0
- package/src/i18n/generated/errors.json +14 -0
- package/src/i18n/generated/errors.ru.gen.ts +1 -1
- package/src/i18n/keys.ts +8 -0
- package/src/i18n/ru.ts +9 -0
- package/src/index.ts +27 -0
- package/src/model/engagement.ts +118 -0
- package/src/model/queries.ts +41 -0
- package/src/model/queryKeys.ts +14 -0
package/dist/api/types.d.ts
CHANGED
|
@@ -36,10 +36,91 @@ import type { FeatureConfig, FeatureDef, FeatureValueDto } from "@stapel/attribu
|
|
|
36
36
|
import type { components } from "./generated/schema.js";
|
|
37
37
|
/** The generated schema table — the one source of truth for wire shapes. */
|
|
38
38
|
export type Schemas = components["schemas"];
|
|
39
|
+
/**
|
|
40
|
+
* The engagement fields, named off the GENERATED row so a rename upstream
|
|
41
|
+
* breaks the build here rather than going quiet on a grid.
|
|
42
|
+
*
|
|
43
|
+
* These were hand-written mirrors while this pair's contract pin sat at
|
|
44
|
+
* `>=0.12 <0.13` and the emitted schema could not see stapel-listings
|
|
45
|
+
* 0.16/0.17. The pin has landed, so the mirrors are gone and the names come
|
|
46
|
+
* from `Schemas["ListingCard"]`. What is kept is the OPTIONALITY, and that is
|
|
47
|
+
* not laziness:
|
|
48
|
+
*
|
|
49
|
+
* **A generated type is a promise about the contract, not about the bytes a
|
|
50
|
+
* particular deployment sends.** The schema says every card carries `viewed`
|
|
51
|
+
* and `view_count`; a storefront pointed at a server still running 0.15 gets
|
|
52
|
+
* rows without them, and the two surfaces this feature exists for — a feed
|
|
53
|
+
* and a SERP — are served by the SEARCH index, whose stored document carries
|
|
54
|
+
* neither by construction. `Partial` is what keeps `model/engagement.ts`
|
|
55
|
+
* allowed to ask, and every reader there answers "absent" with the same
|
|
56
|
+
* silence it answers `null` with.
|
|
57
|
+
*/
|
|
58
|
+
export type ListingEngagementFields = Partial<Pick<Schemas["ListingCard"], "viewed" | "view_count">>;
|
|
59
|
+
/** A generated row with its engagement fields relaxed to optional — see
|
|
60
|
+
* {@link ListingEngagementFields} for why every row type below is spelled
|
|
61
|
+
* this way rather than taken from `Schemas` whole. */
|
|
62
|
+
type WithOptionalEngagement<Row extends ListingEngagementFields> = Omit<Row, keyof ListingEngagementFields> & ListingEngagementFields;
|
|
63
|
+
/**
|
|
64
|
+
* The per-viewer overlay for ONE listing, as the batch read answers it.
|
|
65
|
+
*
|
|
66
|
+
* Note what is REQUIRED here and optional on the row: the overlay always
|
|
67
|
+
* carries all three keys, because it is the answer to a question that was
|
|
68
|
+
* actually asked. A row that carries none of them was serialized by a build
|
|
69
|
+
* that had never heard of them.
|
|
70
|
+
*/
|
|
71
|
+
export type ListingEngagement = Schemas["ListingEngagement"];
|
|
72
|
+
/**
|
|
73
|
+
* `GET /listings/engagement/?ids=…` 200 — `{listing id: overlay}`.
|
|
74
|
+
*
|
|
75
|
+
* The keys are the ids as STRINGS (a DRF `DictField`), and an id with no
|
|
76
|
+
* listing is simply absent rather than present-and-empty. Both facts are why
|
|
77
|
+
* `model/engagement.ts` looks entries up through one function instead of
|
|
78
|
+
* indexing the object at call sites.
|
|
79
|
+
*/
|
|
80
|
+
export type ListingEngagementBatch = Schemas["ListingEngagementBatch"];
|
|
81
|
+
/**
|
|
82
|
+
* The most ids one overlay call may carry — `ENGAGEMENT_BATCH_LIMIT`
|
|
83
|
+
* upstream, which silently TRUNCATES anything longer ("one page of cards, not
|
|
84
|
+
* a crawl of the board").
|
|
85
|
+
*
|
|
86
|
+
* Mirrored here so the truncation happens where it can be seen and named
|
|
87
|
+
* rather than in a response that quietly came back short. A page of more than
|
|
88
|
+
* a hundred cards is not a page; a caller that has one should ask per screen.
|
|
89
|
+
*/
|
|
90
|
+
export declare const LISTINGS_ENGAGEMENT_BATCH_LIMIT = 100;
|
|
91
|
+
/**
|
|
92
|
+
* Normalize the ids on a page into the exact list the request sends.
|
|
93
|
+
*
|
|
94
|
+
* Sorted, de-duplicated, non-integer values dropped, capped at
|
|
95
|
+
* {@link LISTINGS_ENGAGEMENT_BATCH_LIMIT}. Sorting is what makes the cache
|
|
96
|
+
* key honest: the answer is a MAP keyed by id, so two renders that ask for
|
|
97
|
+
* the same ids in different orders are asking the identical question and must
|
|
98
|
+
* not buy two cache entries and two requests. De-duplication is the same
|
|
99
|
+
* argument for a grid that shows one listing twice (a promoted slot above the
|
|
100
|
+
* organic result it also occupies).
|
|
101
|
+
*
|
|
102
|
+
* `queryKeys.engagement` and `api.engagement` are both built from THIS, so
|
|
103
|
+
* the key cannot drift from the request it stands for.
|
|
104
|
+
*/
|
|
105
|
+
export declare function engagementIds(ids: readonly number[]): readonly number[];
|
|
39
106
|
/** `GET /listings/{pk}/` 200 — everything a detail page reads. */
|
|
40
|
-
export type ListingDetail = Schemas["ListingDetail"]
|
|
41
|
-
/**
|
|
42
|
-
|
|
107
|
+
export type ListingDetail = WithOptionalEngagement<Schemas["ListingDetail"]>;
|
|
108
|
+
/**
|
|
109
|
+
* One row of a card list (`GET /listings/`, `GET /listings/my/favorites/`) —
|
|
110
|
+
* and the PROP every card component in this pair takes, which is why its
|
|
111
|
+
* engagement fields are optional where the schema makes them required.
|
|
112
|
+
*
|
|
113
|
+
* The schema is right about this module's own responses: `GET /listings/`
|
|
114
|
+
* carries `viewed` and `view_count` on every row. But the card components are
|
|
115
|
+
* handed rows from elsewhere, and the most important elsewhere is the SEARCH
|
|
116
|
+
* index — `@stapel/search-react` fills `renderCard` with a stored document
|
|
117
|
+
* that cannot hold a per-reader flag by construction. Requiring the fields on
|
|
118
|
+
* the prop would make the pair's primary consumer unable to satisfy its own
|
|
119
|
+
* type, for data no one can supply; it is precisely the case
|
|
120
|
+
* `<ListingEngagementScope>` exists to answer. A deployment running a server
|
|
121
|
+
* older than 0.16 is the same shape of fact.
|
|
122
|
+
*/
|
|
123
|
+
export type ListingCard = WithOptionalEngagement<Schemas["ListingCard"]>;
|
|
43
124
|
/** `POST /listings/` request+response and `POST /{pk}/save-draft/` response —
|
|
44
125
|
* the draft twin. Every user-editable field is a `*_draft` one, promoted onto
|
|
45
126
|
* its published sibling by `publish`. */
|
|
@@ -62,7 +143,9 @@ export type FavoriteToggleResponse = Schemas["FavoriteToggleResponse"];
|
|
|
62
143
|
export type DeleteResponse = Schemas["DeleteResponse"];
|
|
63
144
|
/** The keyset envelope both public card lists come back in
|
|
64
145
|
* (`IDAnchorPagination`). */
|
|
65
|
-
export type PaginatedListingCards = Schemas["PaginatedListingCardList"]
|
|
146
|
+
export type PaginatedListingCards = Omit<Schemas["PaginatedListingCardList"], "items"> & {
|
|
147
|
+
items: ListingCard[];
|
|
148
|
+
};
|
|
66
149
|
/**
|
|
67
150
|
* One row of `GET /listings/my/listings/` — the OWNER's card.
|
|
68
151
|
*
|
|
@@ -80,11 +163,13 @@ export type PaginatedListingCards = Schemas["PaginatedListingCardList"];
|
|
|
80
163
|
* tab keyed off them is a column of blank rows. `myListingTitle` /
|
|
81
164
|
* `myListingPrice` (`model/mine.ts`) are the one place the fallback lives.
|
|
82
165
|
*/
|
|
83
|
-
export type MyListingCard = Schemas["MyListingCard"]
|
|
166
|
+
export type MyListingCard = WithOptionalEngagement<Schemas["MyListingCard"]>;
|
|
84
167
|
/** The keyset envelope `GET /listings/my/listings/` comes back in — the same
|
|
85
168
|
* `IDAnchorPagination` shape as {@link PaginatedListingCards}, over the owner
|
|
86
169
|
* row. */
|
|
87
|
-
export type PaginatedMyListingCards = Schemas["PaginatedMyListingCardList"]
|
|
170
|
+
export type PaginatedMyListingCards = Omit<Schemas["PaginatedMyListingCardList"], "items"> & {
|
|
171
|
+
items: MyListingCard[];
|
|
172
|
+
};
|
|
88
173
|
/**
|
|
89
174
|
* The nine lifecycle states, as `models.ListingStatus` declares them.
|
|
90
175
|
*
|
package/dist/api/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,4EAA4E;AAC5E,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;AAE5C,kEAAkE;AAClE,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"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"}
|
package/dist/api/types.js
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The most ids one overlay call may carry — `ENGAGEMENT_BATCH_LIMIT`
|
|
3
|
+
* upstream, which silently TRUNCATES anything longer ("one page of cards, not
|
|
4
|
+
* a crawl of the board").
|
|
5
|
+
*
|
|
6
|
+
* Mirrored here so the truncation happens where it can be seen and named
|
|
7
|
+
* rather than in a response that quietly came back short. A page of more than
|
|
8
|
+
* a hundred cards is not a page; a caller that has one should ask per screen.
|
|
9
|
+
*/
|
|
10
|
+
export const LISTINGS_ENGAGEMENT_BATCH_LIMIT = 100;
|
|
11
|
+
/**
|
|
12
|
+
* Normalize the ids on a page into the exact list the request sends.
|
|
13
|
+
*
|
|
14
|
+
* Sorted, de-duplicated, non-integer values dropped, capped at
|
|
15
|
+
* {@link LISTINGS_ENGAGEMENT_BATCH_LIMIT}. Sorting is what makes the cache
|
|
16
|
+
* key honest: the answer is a MAP keyed by id, so two renders that ask for
|
|
17
|
+
* the same ids in different orders are asking the identical question and must
|
|
18
|
+
* not buy two cache entries and two requests. De-duplication is the same
|
|
19
|
+
* argument for a grid that shows one listing twice (a promoted slot above the
|
|
20
|
+
* organic result it also occupies).
|
|
21
|
+
*
|
|
22
|
+
* `queryKeys.engagement` and `api.engagement` are both built from THIS, so
|
|
23
|
+
* the key cannot drift from the request it stands for.
|
|
24
|
+
*/
|
|
25
|
+
export function engagementIds(ids) {
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
for (const id of ids) {
|
|
28
|
+
if (Number.isInteger(id))
|
|
29
|
+
seen.add(id);
|
|
30
|
+
}
|
|
31
|
+
return [...seen].sort((a, b) => a - b).slice(0, LISTINGS_ENGAGEMENT_BATCH_LIMIT);
|
|
32
|
+
}
|
|
1
33
|
/**
|
|
2
34
|
* The nine lifecycle states, as `models.ListingStatus` declares them.
|
|
3
35
|
*
|
package/dist/api/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AA4FA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAG,CAAC;AAEnD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAAC,GAAsB;IAClD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACrB,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,+BAA+B,CAAC,CAAC;AACnF,CAAC;AAwFD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAsC;IACjE,OAAO;IACP,SAAS;IACT,WAAW;IACX,QAAQ;IACR,SAAS;IACT,MAAM;IACN,UAAU;IACV,SAAS;IACT,UAAU;CACX,CAAC;AAMF;gEACgE;AAChE,MAAM,CAAC,MAAM,mBAAmB,GAAuC;IACrE,SAAS;IACT,UAAU;IACV,UAAU;IACV,cAAc;CACf,CAAC;AAsIF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GateReasonPopover.d.ts","sourceRoot":"","sources":["../../src/default/GateReasonPopover.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GateReasonPopover.d.ts","sourceRoot":"","sources":["../../src/default/GateReasonPopover.tsx"],"names":[],"mappings":"AAuDA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C;0DAC0D;AAC1D,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;oDACgD;IAChD,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,CAAC;IACpC,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;6EACyE;IACzE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;kFAC8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,SAAS,CAAC;CACrE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,YAAY,CAmE7E"}
|
|
@@ -27,11 +27,33 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
27
27
|
* screen (see `ListingCardBlockedReason`). The rule's disables below are the
|
|
28
28
|
* documented decision §2.4 asks for, not a hole.
|
|
29
29
|
*
|
|
30
|
+
* ── Why the disclosure is CONTROLLED: hover opened it, the click shut it ───
|
|
31
|
+
*
|
|
32
|
+
* An uncontrolled popover triggered by hover AND click treats the click as a
|
|
33
|
+
* TOGGLE, so the natural pointer gesture — rest on the control, then press it
|
|
34
|
+
* — opens the disclosure on the hover and closes it again on the press. The
|
|
35
|
+
* overlay comes back marked `ant-popover-hidden` and the person has pressed
|
|
36
|
+
* the one control that was supposed to explain itself and watched it go
|
|
37
|
+
* blank. Reproduced in jsdom and gated by `test/favoriteGesture.test.tsx`.
|
|
38
|
+
*
|
|
39
|
+
* It needs the DELAY to reproduce, which is the interesting part: antd opens
|
|
40
|
+
* a hover popover after 0.1s, so two events fired back to back (a thumb's
|
|
41
|
+
* emulated `mouseenter` + `click`) land while nothing is open yet and the
|
|
42
|
+
* click opens it correctly. A cursor that lingers is past the delay, and its
|
|
43
|
+
* click closes what the hover opened. The suite was green throughout because
|
|
44
|
+
* every existing test fired `click` alone — a synthetic click carries no
|
|
45
|
+
* hover in front of it, so it exercised the one ordering that worked.
|
|
46
|
+
*
|
|
47
|
+
* So `open` is this component's own state and activation is MONOTONIC: a
|
|
48
|
+
* click may only open the disclosure, never close it. Hover-out, blur and a
|
|
49
|
+
* click outside still close it, because those arrive as an `onOpenChange`
|
|
50
|
+
* that no activation is claiming.
|
|
51
|
+
*
|
|
30
52
|
* Deliberately NOT exported from `src/default/index.ts`: it is how this
|
|
31
53
|
* pair's own gated hearts speak, not a control a host composes with — the
|
|
32
54
|
* same standing `favorite.tsx` has.
|
|
33
55
|
*/
|
|
34
|
-
import { useId } from "react";
|
|
56
|
+
import { useCallback, useId, useRef, useState } from "react";
|
|
35
57
|
import { Flex, Typography } from "antd";
|
|
36
58
|
// eslint-disable-next-line stapel/no-tooltip-in-skin -- interaction disclosure, not a hover: opens on click/tap and focus too, anchored to a non-disabled control, with the reason kept in the a11y tree via aria-describedby (see this file's header)
|
|
37
59
|
import { Popover } from "antd";
|
|
@@ -40,6 +62,29 @@ import { spacing } from "@stapel/tokens";
|
|
|
40
62
|
import { SignInLink } from "./SignInLink.js";
|
|
41
63
|
export function GateReasonPopover(props) {
|
|
42
64
|
const reasonId = useId();
|
|
43
|
-
|
|
65
|
+
const [open, setOpen] = useState(false);
|
|
66
|
+
// Set in the capture phase of a click, i.e. BEFORE the popover's own
|
|
67
|
+
// handler runs on the same event, and dropped once that event is fully
|
|
68
|
+
// dispatched. While it is set, "close" is the tap's own toggle and is
|
|
69
|
+
// refused; see the file header.
|
|
70
|
+
const activating = useRef(false);
|
|
71
|
+
const handleOpenChange = useCallback((next) => {
|
|
72
|
+
if (!next && activating.current)
|
|
73
|
+
return;
|
|
74
|
+
setOpen(next);
|
|
75
|
+
}, []);
|
|
76
|
+
const activate = useCallback(() => {
|
|
77
|
+
activating.current = true;
|
|
78
|
+
setOpen(true);
|
|
79
|
+
// A microtask runs after the whole event dispatch, so the flag covers
|
|
80
|
+
// exactly this gesture and no later hover-out.
|
|
81
|
+
queueMicrotask(() => {
|
|
82
|
+
activating.current = false;
|
|
83
|
+
});
|
|
84
|
+
}, []);
|
|
85
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { id: reasonId, style: visuallyHidden, children: props.reason }), _jsx(Popover, { trigger: ["hover", "focus", "click"], open: open, onOpenChange: handleOpenChange, content: _jsxs(Flex, { vertical: true, gap: spacing[1], "data-testid": props.testId, children: [_jsx(Typography.Text, { children: props.reason }), _jsx(SignInLink, { cta: props.cta, testId: props.signInTestId })] }), children: _jsx("div", { onClickCapture: activate, onKeyDownCapture: (event) => {
|
|
86
|
+
if (event.key === "Enter" || event.key === " ")
|
|
87
|
+
activate();
|
|
88
|
+
}, children: props.children({ "aria-describedby": reasonId }) }) })] }));
|
|
44
89
|
}
|
|
45
90
|
//# sourceMappingURL=GateReasonPopover.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GateReasonPopover.js","sourceRoot":"","sources":["../../src/default/GateReasonPopover.tsx"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"GateReasonPopover.js","sourceRoot":"","sources":["../../src/default/GateReasonPopover.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE7D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACxC,uPAAuP;AACvP,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwB7C,MAAM,UAAU,iBAAiB,CAAC,KAA6B;IAC7D,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAC;IACzB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxC,qEAAqE;IACrE,uEAAuE;IACvE,sEAAsE;IACtE,gCAAgC;IAChC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjC,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,IAAa,EAAQ,EAAE;QAC3D,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,OAAO;YAAE,OAAO;QACxC,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAS,EAAE;QACtC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;QACd,sEAAsE;QACtE,+CAA+C;QAC/C,cAAc,CAAC,GAAG,EAAE;YAClB,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,8BAIE,eAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,YACtC,KAAK,CAAC,MAAM,GACR,EAEP,KAAC,OAAO,IACN,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EACpC,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,gBAAgB,EAC9B,OAAO,EACL,MAAC,IAAI,IAAC,QAAQ,QAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,iBAAe,KAAK,CAAC,MAAM,aACvD,KAAC,UAAU,CAAC,IAAI,cAAE,KAAK,CAAC,MAAM,GAAmB,EACjD,KAAC,UAAU,IAAC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,YAAY,GAAI,IACrD,YAeT,cACE,cAAc,EAAE,QAAQ,EACxB,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE;wBAC1B,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG;4BAAE,QAAQ,EAAE,CAAC;oBAC7D,CAAC,YAEA,KAAK,CAAC,QAAQ,CAAC,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC,GAC7C,GACE,IACT,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -93,7 +93,11 @@
|
|
|
93
93
|
* `<GatedControl>`, which renders it as text linked by `aria-describedby` —
|
|
94
94
|
* a disabled antd button receives no pointer events and is not focusable, so
|
|
95
95
|
* the `Tooltip` this component used to offer as a "quieter" volume was a
|
|
96
|
-
* reason nobody could read on any device.
|
|
96
|
+
* reason nobody could read on any device. The heart itself is never
|
|
97
|
+
* html-`disabled` in ANY volume for the same reason: it carries
|
|
98
|
+
* `aria-disabled` and a live handler, and refuses on ACTIVATION (the gate
|
|
99
|
+
* makes `toggle` a no-op). An inert control cannot be tapped, cannot be
|
|
100
|
+
* focused, and cannot disclose anything. That arm is gone; `blockedReason`
|
|
97
101
|
* is "text" (reason + door), "line" (reason alone, for a grid), or
|
|
98
102
|
* "popover" — the reason and the door disclosed on the heart itself, with
|
|
99
103
|
* the reason still in the accessibility tree. The third arm is NOT the old
|
|
@@ -188,6 +192,33 @@ export declare const CARD_MAIN_CLASS = "stapel-listing-card-main";
|
|
|
188
192
|
/** Added to the frame by a card whose media is FULL-BLEED when stacked
|
|
189
193
|
* (`<ListingCard>`), so the row arm gets the inset the stacked arm does not. */
|
|
190
194
|
export declare const CARD_BLEED_CLASS = "stapel-listing-card-bleed";
|
|
195
|
+
/**
|
|
196
|
+
* The class an ALREADY-SEEN card carries — the mark every mature classified
|
|
197
|
+
* puts on a result you have opened before, and the reason a shopper on their
|
|
198
|
+
* third evening of the same search is not re-reading the same twenty offers.
|
|
199
|
+
*
|
|
200
|
+
* All three card surfaces take it, and all three take it on their OUTER box,
|
|
201
|
+
* so the rule below can reach the photo and the reading column while leaving
|
|
202
|
+
* the favourite heart at full strength: a dimmed heart reads as a disabled
|
|
203
|
+
* heart, and the one control on the card must not look switched off because
|
|
204
|
+
* the card behind it is old news.
|
|
205
|
+
*/
|
|
206
|
+
export declare const CARD_VIEWED_CLASS = "stapel-listing-card-seen";
|
|
207
|
+
/**
|
|
208
|
+
* How far a seen card is dimmed.
|
|
209
|
+
*
|
|
210
|
+
* OPACITY and not a colour, which is the whole reason this needs no dark-mode
|
|
211
|
+
* arm: opacity composites the card against whatever ground the theme painted,
|
|
212
|
+
* so the same number is a grey card on a white page and a dim card on a black
|
|
213
|
+
* one. A `color` here would have to be picked twice, and the second pick is
|
|
214
|
+
* the one nobody photographs. 0.55 is far enough to sort seen from unseen at
|
|
215
|
+
* a glance and not so far that the price stops being readable — a seen
|
|
216
|
+
* listing is still a listing, not a disabled control.
|
|
217
|
+
*
|
|
218
|
+
* A host that wants another value sets `--listing-viewed-opacity` on any
|
|
219
|
+
* ancestor; the rule reads the property and falls back to this.
|
|
220
|
+
*/
|
|
221
|
+
export declare const LISTING_VIEWED_OPACITY = 0.55;
|
|
191
222
|
/** The `href` the hoisted card stylesheet is deduplicated by. */
|
|
192
223
|
export declare const CARD_TARGET_STYLE_HREF = "stapel-listings-card-target";
|
|
193
224
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListingCard.d.ts","sourceRoot":"","sources":["../../src/default/ListingCard.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ListingCard.d.ts","sourceRoot":"","sources":["../../src/default/ListingCard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Card, Flex, Typography, theme as antdTheme } from "antd";
|
|
3
3
|
import { SkinButton as Button } from "@stapel/tokens-antd/skin";
|
|
4
|
-
import { GatedControl, SkinTheme } from "@stapel/tokens-antd/skin";
|
|
4
|
+
import { ErrorAlert, GatedControl, SkinTheme } from "@stapel/tokens-antd/skin";
|
|
5
5
|
import { useActionGate, useT } from "@stapel/core";
|
|
6
6
|
import { spacing } from "@stapel/tokens";
|
|
7
7
|
import { FeatureBadges } from "@stapel/attributes-react/default";
|
|
8
8
|
import { asFeatureDaoList, featuresDtoFromDaoList, featuresFromDaoList } from "../model/features.js";
|
|
9
9
|
import { lifecycleCaption } from "../model/status.js";
|
|
10
|
+
import { isListingViewed } from "../model/engagement.js";
|
|
11
|
+
import { useEngagedListing } from "../headless/Engagement.js";
|
|
10
12
|
import { useFavoriteToggle } from "../headless/Favorites.js";
|
|
11
13
|
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
12
14
|
import { GateReasonPopover } from "./GateReasonPopover.js";
|
|
@@ -29,6 +31,33 @@ export const CARD_MAIN_CLASS = "stapel-listing-card-main";
|
|
|
29
31
|
/** Added to the frame by a card whose media is FULL-BLEED when stacked
|
|
30
32
|
* (`<ListingCard>`), so the row arm gets the inset the stacked arm does not. */
|
|
31
33
|
export const CARD_BLEED_CLASS = "stapel-listing-card-bleed";
|
|
34
|
+
/**
|
|
35
|
+
* The class an ALREADY-SEEN card carries — the mark every mature classified
|
|
36
|
+
* puts on a result you have opened before, and the reason a shopper on their
|
|
37
|
+
* third evening of the same search is not re-reading the same twenty offers.
|
|
38
|
+
*
|
|
39
|
+
* All three card surfaces take it, and all three take it on their OUTER box,
|
|
40
|
+
* so the rule below can reach the photo and the reading column while leaving
|
|
41
|
+
* the favourite heart at full strength: a dimmed heart reads as a disabled
|
|
42
|
+
* heart, and the one control on the card must not look switched off because
|
|
43
|
+
* the card behind it is old news.
|
|
44
|
+
*/
|
|
45
|
+
export const CARD_VIEWED_CLASS = "stapel-listing-card-seen";
|
|
46
|
+
/**
|
|
47
|
+
* How far a seen card is dimmed.
|
|
48
|
+
*
|
|
49
|
+
* OPACITY and not a colour, which is the whole reason this needs no dark-mode
|
|
50
|
+
* arm: opacity composites the card against whatever ground the theme painted,
|
|
51
|
+
* so the same number is a grey card on a white page and a dim card on a black
|
|
52
|
+
* one. A `color` here would have to be picked twice, and the second pick is
|
|
53
|
+
* the one nobody photographs. 0.55 is far enough to sort seen from unseen at
|
|
54
|
+
* a glance and not so far that the price stops being readable — a seen
|
|
55
|
+
* listing is still a listing, not a disabled control.
|
|
56
|
+
*
|
|
57
|
+
* A host that wants another value sets `--listing-viewed-opacity` on any
|
|
58
|
+
* ancestor; the rule reads the property and falls back to this.
|
|
59
|
+
*/
|
|
60
|
+
export const LISTING_VIEWED_OPACITY = 0.55;
|
|
32
61
|
/** The `href` the hoisted card stylesheet is deduplicated by. */
|
|
33
62
|
export const CARD_TARGET_STYLE_HREF = "stapel-listings-card-target";
|
|
34
63
|
/**
|
|
@@ -91,6 +120,12 @@ export function cardTargetCss() {
|
|
|
91
120
|
`${bleed}{padding-block:var(--listing-card-inset);` +
|
|
92
121
|
`padding-inline-start:var(--listing-card-inset)}` +
|
|
93
122
|
`}`,
|
|
123
|
+
// Already seen. The photo and everything inside the card's anchor dim
|
|
124
|
+
// together; the heart is outside the anchor and stays as it was — see
|
|
125
|
+
// CARD_VIEWED_CLASS.
|
|
126
|
+
`.${CARD_VIEWED_CLASS} .${CARD_TARGET_CLASS},` +
|
|
127
|
+
`.${CARD_VIEWED_CLASS} .${CARD_MEDIA_CLASS}` +
|
|
128
|
+
`{opacity:var(--listing-viewed-opacity,${String(LISTING_VIEWED_OPACITY)})}`,
|
|
94
129
|
].join("");
|
|
95
130
|
}
|
|
96
131
|
/** The anchor: a block that inherits the card's own type colour rather than
|
|
@@ -154,7 +189,10 @@ export function CardTarget(props) {
|
|
|
154
189
|
}
|
|
155
190
|
export function ListingCard(props) {
|
|
156
191
|
const t = useT();
|
|
157
|
-
|
|
192
|
+
// The engagement scope's answer laid over the row, when a container opened
|
|
193
|
+
// one — the same object otherwise. A card fed by search carries no
|
|
194
|
+
// `viewed`/`is_favorited` of its own; see `headless/Engagement.tsx`.
|
|
195
|
+
const listing = useEngagedListing(props.listing);
|
|
158
196
|
const favorite = useFavoriteToggle(listing.id, listing.is_favorited);
|
|
159
197
|
const { token } = antdTheme.useToken();
|
|
160
198
|
const badgeDaos = asFeatureDaoList(listing.features_badges);
|
|
@@ -170,6 +208,13 @@ export function ListingCard(props) {
|
|
|
170
208
|
const status = listing.status === undefined ? undefined : lifecycleCaption(listing.status);
|
|
171
209
|
const favoriteGate = useActionGate(favorite.gate);
|
|
172
210
|
const blockedReason = props.blockedReason ?? "text";
|
|
211
|
+
// Absent on every response the fleet answers today, and a plain `false`
|
|
212
|
+
// when it is — no dimming, no attribute, nothing said about it.
|
|
213
|
+
const viewed = isListingViewed(listing);
|
|
214
|
+
// A saved heart is a SOLID accent shape; an unsaved one is the outline it
|
|
215
|
+
// has always been. `is_favorited: null` reads as unsaved (`favorite`
|
|
216
|
+
// resolves the third state), never as a look of its own.
|
|
217
|
+
const heartIcon = (_jsx(HeartIcon, { filled: favorite.favorited, ...(favorite.favorited ? { color: token.colorPrimary } : {}) }));
|
|
173
218
|
const favoriteLabel = t(favorite.favorited
|
|
174
219
|
? LISTINGS_I18N_KEYS.cardFavoriteRemove
|
|
175
220
|
: LISTINGS_I18N_KEYS.cardFavoriteAdd);
|
|
@@ -193,6 +238,8 @@ export function ListingCard(props) {
|
|
|
193
238
|
listing.location_label.length > 0 ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "listings-card-location", children: listing.location_label })) : null] }));
|
|
194
239
|
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-card", "data-listing-id": listing.id, ...(status !== undefined
|
|
195
240
|
? { "data-listing-status": status.status }
|
|
241
|
+
: {}), ...(viewed
|
|
242
|
+
? { className: CARD_VIEWED_CLASS, "data-listing-viewed": "true" }
|
|
196
243
|
: {}),
|
|
197
244
|
// The body's own padding is zero because the frame fills the card and
|
|
198
245
|
// the photo runs edge to edge when it is stacked: padding here would
|
|
@@ -202,19 +249,23 @@ export function ListingCard(props) {
|
|
|
202
249
|
styles: { body: { minWidth: 0, padding: 0 } }, style: {
|
|
203
250
|
["--listing-card-focus"]: token.colorPrimary,
|
|
204
251
|
["--listing-card-inset"]: `${String(token.paddingSM)}px`,
|
|
205
|
-
}, children: _jsx("div", { className: CARD_QUERY_CLASS, children: _jsxs("div", { className: `${CARD_FRAME_CLASS} ${CARD_BLEED_CLASS}`, children: [_jsx("div", { className: CARD_MEDIA_CLASS, children: _jsx(ListingPhotoStrip, { images: listing.images ?? [], title: title.length > 0 ? title : String(listing.id), testId: "listings-card-photos" }) }), _jsxs("div", { className: CARD_MAIN_CLASS, children: [_jsx(CardTarget, { ...props, listingId: listing.id, label: targetLabel, children: content }), props.showFavorite === false ? null : (
|
|
252
|
+
}, children: _jsx("div", { className: CARD_QUERY_CLASS, children: _jsxs("div", { className: `${CARD_FRAME_CLASS} ${CARD_BLEED_CLASS}`, children: [_jsx("div", { className: CARD_MEDIA_CLASS, children: _jsx(ListingPhotoStrip, { images: listing.images ?? [], title: title.length > 0 ? title : String(listing.id), testId: "listings-card-photos" }) }), _jsxs("div", { className: CARD_MAIN_CLASS, children: [_jsx(CardTarget, { ...props, listingId: listing.id, label: targetLabel, children: content }), props.showFavorite === false ? null : (_jsxs("div", { style: {
|
|
206
253
|
paddingInline: token.paddingSM,
|
|
207
254
|
paddingBlockEnd: token.paddingSM,
|
|
208
|
-
}, children: blockedReason === "popover" &&
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
255
|
+
}, children: [blockedReason === "popover" &&
|
|
256
|
+
favoriteGate.reason !== undefined ? (
|
|
257
|
+
/* The third volume: nothing standing in the layout. The
|
|
258
|
+
heart is `aria-disabled`, NOT `disabled` — the gate
|
|
259
|
+
already refuses the action (`toggle` is a no-op while
|
|
260
|
+
blocked), and an html-disabled button would swallow
|
|
261
|
+
the hover, the focus and the tap the disclosure opens
|
|
262
|
+
on, which is the exact grave the old Tooltip died in. */
|
|
263
|
+
_jsx(GateReasonPopover, { reason: favoriteGate.reason, cta: props.signIn, testId: "listings-card-favorite-reason", signInTestId: "listings-card-sign-in", children: (bind) => (_jsx(Flex, { justify: "flex-end", style: { width: "100%" }, children: _jsx(Button, { "aria-disabled": true, ...bind, "aria-label": favoriteLabel, "aria-pressed": favorite.favorited, "data-testid": "listings-card-favorite", "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: heartIcon }) })) })) : (_jsxs(_Fragment, { children: [_jsx(GatedControl, { gate: favorite.gate, testId: "listings-card-actions", style: { width: "100%" }, children: (bind) => (_jsx(Flex, { justify: "flex-end", style: { width: "100%" }, children: _jsx(Button
|
|
264
|
+
// The substrate's binding, spread whole: it now
|
|
265
|
+
// owns "never inert" for the whole fleet —
|
|
266
|
+
// `disabled` stays false, `aria-disabled` and
|
|
267
|
+
// `tabIndex` come from it, and the reason is
|
|
268
|
+
// wired by `aria-describedby`.
|
|
269
|
+
, { ...bind, "aria-label": favoriteLabel, "aria-pressed": favorite.favorited, "data-testid": "listings-card-favorite", "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: heartIcon }) })) }), favoriteGate.reason === undefined || blockedReason === "line" ? null : (_jsx(Typography.Text, { type: "secondary", "data-testid": "listings-card-favorite-blocked", children: _jsx(SignInLink, { cta: props.signIn, testId: "listings-card-sign-in" }) }))] })), _jsx(ErrorAlert, { testId: "listings-card-favorite-error", thrown: favorite.error, variant: "inline" })] }))] })] }) }) })] }));
|
|
219
270
|
}
|
|
220
271
|
//# sourceMappingURL=ListingCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListingCard.js","sourceRoot":"","sources":["../../src/default/ListingCard.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"ListingCard.js","sourceRoot":"","sources":["../../src/default/ListingCard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListingDetailPane.d.ts","sourceRoot":"","sources":["../../src/default/ListingDetailPane.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAiBrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAc9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtE;;oBAEoB;AACpB,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAE1C;;;;iCAIiC;AACjC,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC,MAAM,WAAW,sBACf,SAAQ,aAAa,EACnB,oBAAoB;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;6BAEyB;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IACrC;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IACjC;6EACyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;;sDAEkD;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,YAAY,CA+
|
|
1
|
+
{"version":3,"file":"ListingDetailPane.d.ts","sourceRoot":"","sources":["../../src/default/ListingDetailPane.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAiBrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAc9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtE;;oBAEoB;AACpB,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAE1C;;;;iCAIiC;AACjC,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC,MAAM,WAAW,sBACf,SAAQ,aAAa,EACnB,oBAAoB;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;6BAEyB;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IACrC;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IACjC;6EACyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;;sDAEkD;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,YAAY,CA+gB7E"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Descriptions, Divider, Flex, Typography } from "antd";
|
|
2
|
+
import { Descriptions, Divider, Flex, Typography, theme as antdTheme } from "antd";
|
|
3
3
|
import { SkinButton as Button } from "@stapel/tokens-antd/skin";
|
|
4
4
|
import { ErrorAlert, EmptyState, GatedButton, GatedControl, SkinTheme, } from "@stapel/tokens-antd/skin";
|
|
5
5
|
import { SlotPlaceholder, matchLoad, useActionGate, useI18n, useT, } from "@stapel/core";
|
|
@@ -45,6 +45,7 @@ export const DETAIL_PHOTO_MIN = "14rem";
|
|
|
45
45
|
export function ListingDetailPane(props) {
|
|
46
46
|
const t = useT();
|
|
47
47
|
const { locale } = useI18n();
|
|
48
|
+
const { token } = antdTheme.useToken();
|
|
48
49
|
const bag = useListingDetail(props.id, {
|
|
49
50
|
...(props.viewerId !== undefined ? { viewerId: props.viewerId } : {}),
|
|
50
51
|
...(props.categoryFeatures !== undefined
|
|
@@ -58,9 +59,13 @@ export function ListingDetailPane(props) {
|
|
|
58
59
|
// leaves rendering the reason to `<GatedControl>`, which computes its own.
|
|
59
60
|
const favoriteView = useActionGate(bag.favoriteGate);
|
|
60
61
|
const split = props.layout === "split";
|
|
61
|
-
const favoriteLabel = t(bag.isFavorited
|
|
62
|
+
const favoriteLabel = t(bag.isFavorited
|
|
62
63
|
? LISTINGS_I18N_KEYS.cardFavoriteRemove
|
|
63
64
|
: LISTINGS_I18N_KEYS.cardFavoriteAdd);
|
|
65
|
+
// Saved is a SOLID accent shape; not-saved (and `is_favorited: null`, which
|
|
66
|
+
// is "nobody asked", not "no") is the outline. The bag flips it on the
|
|
67
|
+
// gesture and rolls it back if the write fails — see `useListingDetail`.
|
|
68
|
+
const heartIcon = (_jsx(HeartIcon, { filled: bag.isFavorited, ...(bag.isFavorited ? { color: token.colorPrimary } : {}) }));
|
|
64
69
|
return (_jsx(SkinTheme, { surface: "base", style: {
|
|
65
70
|
maxWidth: split ? DETAIL_SPLIT_MEASURE : DETAIL_MEASURE,
|
|
66
71
|
padding: spacing[4],
|
|
@@ -121,10 +126,10 @@ export function ListingDetailPane(props) {
|
|
|
121
126
|
rather than `disabled`, so the disclosure's hover, focus
|
|
122
127
|
and tap all arrive — and the click is a safe no-op, because
|
|
123
128
|
`toggleFavorite` refuses while the gate is blocked. */
|
|
124
|
-
_jsx(GateReasonPopover, { reason: favoriteView.reason, cta: props.signIn, testId: "listings-detail-favorite-reason", signInTestId: "listings-detail-sign-in", children: (bind) => (_jsx(Button, { "aria-disabled": true, ...bind, "aria-label": favoriteLabel, "aria-pressed": bag.isFavorited
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const actionError = actions.error !== undefined && actions.error !== null ? (_jsx(ErrorAlert, { testId: "listings-detail-action-error", thrown: actions.error, variant: "inline" })) : null;
|
|
129
|
+
_jsx(GateReasonPopover, { reason: favoriteView.reason, cta: props.signIn, testId: "listings-detail-favorite-reason", signInTestId: "listings-detail-sign-in", children: (bind) => (_jsx(Button, { "aria-disabled": true, ...bind, "aria-label": favoriteLabel, "aria-pressed": bag.isFavorited, icon: heartIcon, "data-testid": "listings-detail-favorite", "data-favorited": String(bag.isFavorited), "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", onClick: bag.toggleFavorite, children: favoriteLabel })) })) : (_jsxs(Flex, { vertical: true, gap: spacing[1], children: [_jsx(GatedControl, { gate: bag.favoriteGate, testId: "listings-detail-favorite-gate", children: (bind) => (_jsx(Button
|
|
130
|
+
// See `<ListingCard>`: the binding, spread whole.
|
|
131
|
+
, { ...bind, "aria-label": favoriteLabel, "aria-pressed": bag.isFavorited, icon: heartIcon, "data-testid": "listings-detail-favorite", "data-favorited": String(bag.isFavorited), "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", onClick: bag.toggleFavorite, children: favoriteLabel })) }), bag.favoriteGate.available ? null : (_jsx(Typography.Text, { type: "secondary", "data-testid": "listings-detail-favorite-blocked", children: _jsx(SignInLink, { cta: props.signIn, testId: "listings-detail-sign-in" }) }))] })), props.actions] }));
|
|
132
|
+
const actionError = (_jsxs(_Fragment, { children: [actions.error !== undefined && actions.error !== null ? (_jsx(ErrorAlert, { testId: "listings-detail-action-error", thrown: actions.error, variant: "inline" })) : null, _jsx(ErrorAlert, { testId: "listings-detail-favorite-error", thrown: bag.favoriteError, variant: "inline" })] }));
|
|
128
133
|
const description = (_jsxs(_Fragment, { children: [_jsx(Typography.Title, { level: 5, children: t(LISTINGS_I18N_KEYS.detailDescription) }), _jsx(Typography.Paragraph, { "data-testid": "listings-detail-description", children: listing.description ?? "" })] }));
|
|
129
134
|
/* The DISPLAY envelope, not the edit one: a redacted row keeps
|
|
130
135
|
its place in the table and says the seller supplied the value.
|
|
@@ -153,7 +158,7 @@ export function ListingDetailPane(props) {
|
|
|
153
158
|
count: bag.unreadableFeatures,
|
|
154
159
|
}) })) : null] }));
|
|
155
160
|
const meta = (_jsxs(Descriptions, { size: "small", column: 1, children: [listing.stock_quantity != null ? (_jsx(Descriptions.Item, { label: t(LISTINGS_I18N_KEYS.detailStock), children: _jsx("span", { "data-testid": "listings-detail-stock", children: listing.stock_quantity }) })) : null, listing.location_label !== undefined &&
|
|
156
|
-
listing.location_label.length > 0 ? (_jsx(Descriptions.Item, { label: t(LISTINGS_I18N_KEYS.composeLocationLabel), children: listing.location_label })) : null] }));
|
|
161
|
+
listing.location_label.length > 0 ? (_jsx(Descriptions.Item, { label: t(LISTINGS_I18N_KEYS.composeLocationLabel), children: listing.location_label })) : null, bag.viewCount !== undefined ? (_jsx(Descriptions.Item, { label: t(LISTINGS_I18N_KEYS.detailViews), children: _jsx("span", { "data-testid": "listings-detail-views", children: bag.viewCount }) })) : null] }));
|
|
157
162
|
const aside = props.aside !== undefined ? (_jsx("div", { "data-testid": "listings-detail-aside", children: props.aside })) : null;
|
|
158
163
|
if (!split) {
|
|
159
164
|
// The single column, in the order it has always read — the
|