@stapel/listings-react 0.2.0 → 0.3.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/MODULE.md +97 -3
  3. package/README.md +67 -2
  4. package/dist/default/FavoritesPane.d.ts +19 -4
  5. package/dist/default/FavoritesPane.d.ts.map +1 -1
  6. package/dist/default/FavoritesPane.js +13 -3
  7. package/dist/default/FavoritesPane.js.map +1 -1
  8. package/dist/default/ListingCard.d.ts +54 -11
  9. package/dist/default/ListingCard.d.ts.map +1 -1
  10. package/dist/default/ListingCard.js +32 -5
  11. package/dist/default/ListingCard.js.map +1 -1
  12. package/dist/default/ListingComposerPage.d.ts +59 -4
  13. package/dist/default/ListingComposerPage.d.ts.map +1 -1
  14. package/dist/default/ListingComposerPage.js +12 -3
  15. package/dist/default/ListingComposerPage.js.map +1 -1
  16. package/dist/default/SignInLink.d.ts +28 -0
  17. package/dist/default/SignInLink.d.ts.map +1 -0
  18. package/dist/default/SignInLink.js +17 -0
  19. package/dist/default/SignInLink.js.map +1 -0
  20. package/dist/default/index.d.ts +5 -3
  21. package/dist/default/index.d.ts.map +1 -1
  22. package/dist/default/index.js +1 -0
  23. package/dist/default/index.js.map +1 -1
  24. package/dist/headless/ListingComposer.d.ts +17 -0
  25. package/dist/headless/ListingComposer.d.ts.map +1 -1
  26. package/dist/headless/ListingComposer.js +26 -10
  27. package/dist/headless/ListingComposer.js.map +1 -1
  28. package/dist/i18n/es.d.ts.map +1 -1
  29. package/dist/i18n/es.js +1 -0
  30. package/dist/i18n/es.js.map +1 -1
  31. package/dist/i18n/keys.d.ts +2 -0
  32. package/dist/i18n/keys.d.ts.map +1 -1
  33. package/dist/i18n/keys.js +3 -0
  34. package/dist/i18n/keys.js.map +1 -1
  35. package/dist/i18n/ru.d.ts.map +1 -1
  36. package/dist/i18n/ru.js +1 -0
  37. package/dist/i18n/ru.js.map +1 -1
  38. package/llms.txt +1 -1
  39. package/manifest.json +2 -1
  40. package/nav-manifest.json +1 -1
  41. package/package.json +2 -2
  42. package/src/analytics/generated/events.json +1 -1
  43. package/src/default/FavoritesPane.tsx +40 -11
  44. package/src/default/ListingCard.tsx +144 -27
  45. package/src/default/ListingComposerPage.tsx +79 -14
  46. package/src/default/SignInLink.tsx +54 -0
  47. package/src/default/index.ts +15 -3
  48. package/src/headless/ListingComposer.tsx +43 -10
  49. package/src/i18n/es.ts +1 -0
  50. package/src/i18n/keys.ts +3 -0
  51. package/src/i18n/ru.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,134 @@
1
1
  # @stapel/listings-react
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 88a8be4: The composer's category seam runs in both directions, so `/new` can be mounted
8
+
9
+ A seam that only goes one way is not a seam. `ListingComposerPage.categorySlot`
10
+ was a `ReactNode`, and the composer's category moves only through
11
+ `bag.setCategory` — which a node handed in from outside cannot reach. There was
12
+ no `onCategoryChange` either, so a container could neither set the category nor
13
+ learn it, and `features` — the schema OF the chosen category, the entire reason
14
+ the slot exists — was unreachable rather than withheld. The screen rendered and
15
+ could not be used; the storefront named it a gap instead of shipping it (Wave
16
+ D, G-1).
17
+
18
+ Two ways in, and `categorySlot` keeps rendering (deprecated, nothing breaks):
19
+
20
+ ```tsx
21
+ <ListingComposerPage
22
+ category={categoryId === null ? "" : String(categoryId)}
23
+ onCategoryChange={(id) => setCategoryId(id === "" ? null : Number(id))}
24
+ renderCategoryPicker={({ value, setCategory }) => (
25
+ <CategoryPickerField
26
+ value={value === "" ? null : Number(value)}
27
+ onChange={(id) => setCategory(id === null ? "" : String(id))}
28
+ />
29
+ )}
30
+ features={features.data ?? []}
31
+ featuresLoading={features.isPending}
32
+ featuresError={features.error ?? undefined}
33
+ />
34
+ ```
35
+
36
+ `renderCategoryPicker({ value, setCategory })` is the render-prop shape
37
+ `<CategoryPage renderListings>` already uses in the sibling pair. `category` /
38
+ `onCategoryChange` make the hook controlled on that one field, for the
39
+ container that holds the id anyway — it must, because
40
+ `useCategoryFeatures(id)` is keyed by it. `onCategoryChange` fires either way:
41
+ it is the wire the schema read is asked for on.
42
+
43
+ `useListingComposer` takes the same two options, so a host with its own skin
44
+ gets the same seam.
45
+
46
+ The README's example is now the wiring that works, and
47
+ `test/composerCategorySeam.test.tsx` gates it against the props declaration —
48
+ this pair documented `<MediaGalleryField bag={…}>` for a whole release while
49
+ the package had no such prop, and nothing in the suite could tell.
50
+
51
+ - 9230f5f: `<ListingCard>`: one click, one navigation — and a `<Link>` it can be handed
52
+
53
+ `href` and `onOpen` were two optional props and the card rendered BOTH when
54
+ both were given: the handler ran, the container routed, and the browser then
55
+ followed the anchor still sitting on the button. Two navigations for one click.
56
+ The storefront's workaround was `onOpen` alone, which cost the most linkable
57
+ element in the app its anchor — no middle-click, no "open in new tab", nothing
58
+ for a crawler to follow (Wave D, G-2).
59
+
60
+ `ListingCardOpenProps` is now a union with three arms and no fourth:
61
+
62
+ ```tsx
63
+ <ListingCard listing={row} href={`/l/${row.id}`} /> // an anchor
64
+ <ListingCard listing={row} href={`/l/${row.id}`} linkComponent={Link} /> // the host's <Link>
65
+ <ListingCard listing={row} onOpen={(id) => navigate(`/l/${id}`)} /> // a button
66
+ <ListingCard listing={row} /> // no open control
67
+ ```
68
+
69
+ Passing `href` and `onOpen` together no longer typechecks, and neither does a
70
+ `linkComponent` on the callback arm — `linkComponent` IS the link.
71
+
72
+ `linkComponent` is `@stapel/core`'s `LinkComponent`, a component taking a plain
73
+ `href`, so this pair stays router-agnostic and a container keeps a real anchor
74
+ while the click stays inside the SPA:
75
+
76
+ ```tsx
77
+ const RouterLink: LinkComponent = ({ href, children, ...rest }) => (
78
+ <Link to={href} {...rest}>
79
+ {children}
80
+ </Link>
81
+ );
82
+ ```
83
+
84
+ `<FavoritesPane>` takes the same union one level up (`hrefFor` / `onOpen` /
85
+ `linkComponent`), so a pane cannot re-introduce upstream what the card refuses.
86
+
87
+ Breaking only for a caller that passed both props — which is the defect this
88
+ release removes, and which had no correct behaviour to preserve.
89
+
90
+ - 3e2e2a3: A blocked control now carries the door, not just the reason: `signIn`
91
+
92
+ `actionBlocked` ended the grey-rectangle incident by making every switched-off
93
+ control state its reason. It did not end the next one. "Sign in to save this",
94
+ "sign in to leave a review", "sign in to message the seller" are reasons whose
95
+ next action is a LINK, and no pair took one — so the storefront had to put its
96
+ own notice a screen away from each of the three controls it was about, and
97
+ named it a gap rather than shipping it (Wave D, G-3).
98
+
99
+ All three now take the same prop, core's `SignInCta`:
100
+
101
+ ```tsx
102
+ <ListingCard listing={row} signIn={{ href: `/login?next=${here}` }} />
103
+ <ReviewsPanel target={target} signIn={{ href: `/login?next=${here}` }} />
104
+ <StartChatButton sellerId={sellerId} signIn={{ onSignIn: () => openModal() }} />
105
+ ```
106
+
107
+ `{href}` **or** `{onSignIn}`, never both. Omit it and the reason renders alone,
108
+ with no trailing whitespace where the link is not — a host with no sign-in
109
+ route is a supported host.
110
+
111
+ Two more things each pair had to fix to make the door reachable:
112
+
113
+ - **listings**: the favourite's reason lived only in a `title` on a DISABLED
114
+ button, which receives no pointer events in any browser — core's own
115
+ `actionGate.ts` calls that "a reason nobody can read". It is now text beside
116
+ the heart (`listings-card-favorite-blocked`), with the link inside it. The
117
+ heart is still never hidden from a visitor.
118
+ - **chat**: `StartDirectChat` had no mandate gate at all, so a visitor could
119
+ press "message the seller" and collect a 401 — a refusal delivered at the one
120
+ moment it is useless. The axis is now the first arm of its `firstBlock`, read
121
+ through core's `MandateSource` seam. `member` may write; `guest`/`anonymous`
122
+ are told to sign in; `asking` says we are still asking. `unavailable` stays
123
+ AVAILABLE on purpose: that is what core answers outside a `<MandateProvider>`
124
+ too, and a host that never wired the axis must not lose its button — "we
125
+ could not ask" is not "you may not". This raises chat-react's `@stapel/core`
126
+ floor to `>=0.15.0`, where `useMandate`/`matchMandate` shipped.
127
+
128
+ The link's LABEL is each pair's own (`listings.card.sign_in`,
129
+ `reviews.form.sign_in`, `chat.start.sign_in`), in all three locales — core
130
+ floors `en` and `ru`, and these pairs also ship `es`.
131
+
3
132
  ## 0.2.0
4
133
 
5
134
  ### Minor Changes
package/MODULE.md CHANGED
@@ -137,13 +137,107 @@ never import each other (the monorepo README states the direction). So:
137
137
  by `useUploadQueue()`'s bag. `bag.refs` IS the value of `images_draft` (same
138
138
  order, first tile the cover) and `bag.settled` is the submit gate. That pair
139
139
  wrote its bag to this contract on purpose (its §13.6 note 9);
140
- - **the category schema** is a plain `readonly FeatureDef[]`, plus a
141
- `categorySlot` for the chooser;
140
+ - **the category schema** is a plain `readonly FeatureDef[]`, plus
141
+ `renderCategoryPicker` for the chooser (see below);
142
142
  - **a stored image reference** is resolved by a host-supplied
143
- `ListingImageResolver`.
143
+ `ListingImageResolver`;
144
+ - **navigation** is core's `LinkComponent` — a component taking a plain `href`.
145
+ Not react-router: there are several routers and a library that picks one
146
+ picks it for every host.
144
147
 
145
148
  `@stapel/attributes-react` IS a dependency; it is L0, like `@stapel/image`.
146
149
 
150
+ ### 4.0 How a card opens is one contract, and the type enforces it
151
+
152
+ `href` and `onOpen` were two optional props, and a card given both navigated
153
+ TWICE for one click: the handler ran, the container routed, and the browser
154
+ then followed the anchor still sitting on the button. The storefront worked
155
+ around it by passing `onOpen` alone — which cost the most linkable element in
156
+ the app its anchor (no middle-click, no "open in new tab", nothing for a
157
+ crawler) and was named as gap G-2 rather than shipped as a preference.
158
+
159
+ `ListingCardOpenProps` is now a three-armed union — `{href, linkComponent?}`,
160
+ `{onOpen}`, or neither — so exactly one navigation mechanism reaches the DOM
161
+ and "both" does not typecheck. `linkComponent` rides on the link arm because
162
+ it IS the link; handing one to a callback card would be two answers to one
163
+ question again. `<FavoritesPane>` takes the same union one level up
164
+ (`hrefFor` / `onOpen` / `linkComponent`), so a pane cannot re-introduce
165
+ upstream what the card refuses.
166
+
167
+ `test/cardNavigation.test.tsx` asserts on the DOM, not on the props: an anchor
168
+ and no handler, a button and no `href`, a `linkComponent` and no `<a href>` at
169
+ all, plus two `@ts-expect-error` cases for the combinations that used to
170
+ compile.
171
+
172
+ ### 4.1 The category seam has to run in both directions
173
+
174
+ A seam that only goes one way is not a seam. Until 0.3.0 the chooser arrived
175
+ as `categorySlot: ReactNode` and the composer's category moved only through
176
+ `bag.setCategory` — which a node handed in from outside cannot reach. There
177
+ was no `onCategoryChange` either, so the container could neither set the
178
+ category nor learn it, and `features` — the schema OF the chosen category,
179
+ which is the entire reason the slot exists — was unreachable rather than
180
+ withheld. The screen could not be mounted at all (storefront Wave D, G-1).
181
+
182
+ Two ways in, both shipping in 0.3.0:
183
+
184
+ - `renderCategoryPicker({ value, setCategory })` — the render-prop shape
185
+ `<CategoryPage renderListings>` already uses in the sibling pair;
186
+ - `category` / `onCategoryChange` — controlled, for the container that holds
187
+ the id anyway.
188
+
189
+ `categorySlot` still renders (nothing that passed it breaks) and is
190
+ deprecated.
191
+
192
+ **The features wiring, end to end, and it is the container's:**
193
+
194
+ ```tsx
195
+ const [categoryId, setCategoryId] = useState<number | null>(null);
196
+ const features = useCategoryFeatures(categoryId); // @stapel/categories-react
197
+
198
+ <ListingComposerPage
199
+ category={categoryId === null ? "" : String(categoryId)}
200
+ onCategoryChange={(id) => setCategoryId(id === "" ? null : Number(id))}
201
+ renderCategoryPicker={({ value, setCategory }) => (
202
+ <CategoryPickerField
203
+ value={value === "" ? null : Number(value)}
204
+ onChange={(id) => setCategory(id === null ? "" : String(id))}
205
+ />
206
+ )}
207
+ features={features.data ?? []}
208
+ featuresLoading={features.isPending}
209
+ featuresError={features.error ?? undefined}
210
+ />
211
+ ```
212
+
213
+ `useCategoryFeatures` returns `UseQueryResult<readonly CategoryFeature[]>` and
214
+ `CategoryFeature` IS `FeatureDef` (categories-react re-exports the L0 type
215
+ rather than mirroring it), so nothing converts. The three props travel
216
+ together on purpose: `features={[]}` alone would say "this category asks
217
+ nothing", which during the read is a lie the publish gate must not repeat.
218
+
219
+ Category ids cross this seam as STRINGS because that is what
220
+ `Listing.category` carries on the wire; the picker speaks numbers, and the two
221
+ lines above are the whole conversion — done once, in the container, where both
222
+ halves are visible.
223
+
224
+ ### 4.2 A stated reason needs a next action
225
+
226
+ `actionBlocked` ended the grey-rectangle incident: every switched-off control
227
+ states its reason. It did not end the next one. "Sign in to save this" is a
228
+ reason whose next action is a LINK, and no pair took one — so the storefront
229
+ put its own notice a screen away from the three controls it was about (Wave D,
230
+ G-3), and the card's reason lived only in a `title` on a disabled button, which
231
+ receives no pointer events in any browser.
232
+
233
+ Both halves are closed here: the reason renders as text beside the heart, and
234
+ `signIn` — core's `SignInCta`, `{href}` or `{onSignIn}` and never both — is
235
+ rendered inside the same element. `<StartChatButton>` and `<ReviewFormCard>`
236
+ take the identical prop, so a container writes the destination once.
237
+
238
+ The LABEL is this pair's (`listings.card.sign_in`, all three locales) rather
239
+ than core's: core floors `en` and `ru`, and this pair also ships `es`.
240
+
147
241
  ## 5. Gates and their reasons
148
242
 
149
243
  Every write in the pair is behind an `ActionAvailability`. The mandate axis is
package/README.md CHANGED
@@ -68,6 +68,45 @@ container is the seam:
68
68
  that carries each type's display config beside its value. A grid of forty
69
69
  cards therefore costs one query and **no category read**.
70
70
 
71
+ ### How a card opens: one of three, never two
72
+
73
+ ```tsx
74
+ <ListingCard listing={row} href={`/l/${row.id}`} /> // an anchor
75
+ <ListingCard listing={row} href={`/l/${row.id}`} linkComponent={Link} /> // the host's <Link>
76
+ <ListingCard listing={row} onOpen={(id) => navigate(`/l/${id}`)} /> // a button
77
+ <ListingCard listing={row} /> // no open control
78
+ ```
79
+
80
+ `href` and `onOpen` are arms of a union, not two optional props, because a card
81
+ that had both navigated **twice** for one click — the handler ran and the
82
+ browser then followed the anchor. Passing both is now a type error.
83
+
84
+ `linkComponent` is `@stapel/core`'s `LinkComponent`: a component taking a plain
85
+ `href`, so the pair stays router-agnostic and a container keeps a real anchor
86
+ (middle-click, "open in new tab", a crawler) while the click stays inside the
87
+ SPA:
88
+
89
+ ```tsx
90
+ const RouterLink: LinkComponent = ({ href, children, ...rest }) => (
91
+ <Link to={href} {...rest}>{children}</Link>
92
+ );
93
+ ```
94
+
95
+ `<FavoritesPane>` takes the same union (`hrefFor` / `onOpen` / `linkComponent`),
96
+ so a pane cannot re-introduce upstream what the card no longer allows.
97
+
98
+ ### The heart a visitor can see, read, and act on
99
+
100
+ ```tsx
101
+ <ListingCard listing={row} href={`/l/${row.id}`} signIn={{ href: `/login?next=${here}` }} />
102
+ ```
103
+
104
+ The favourite control is never hidden from a visitor — it is switched off, the
105
+ reason is printed as TEXT beside it (a tooltip on a disabled button is a reason
106
+ nobody can read), and `signIn` is the door. `SignInCta` is core's, `{href}`
107
+ **or** `{onSignIn}`, the same prop `@stapel/chat-react` and
108
+ `@stapel/reviews-react` take. Omit it and the reason renders alone.
109
+
71
110
  ## Submitting a listing
72
111
 
73
112
  Four contracts meet on the composer, and three of them arrive as seams rather
@@ -80,17 +119,43 @@ than dependencies:
80
119
  | the value editors | `@stapel/attributes-react` | L0, so a direct dependency |
81
120
 
82
121
  ```tsx
122
+ const [categoryId, setCategoryId] = useState<number | null>(null);
83
123
  const gallery = useUploadQueue({ max: 10 }); // @stapel/cdn-react
84
124
  const features = useCategoryFeatures(categoryId); // @stapel/categories-react
85
125
 
86
126
  <ListingComposerPage
87
- features={features}
127
+ features={features.data ?? []}
128
+ featuresLoading={features.isPending}
129
+ featuresError={features.error ?? undefined}
88
130
  images={gallery}
89
- categorySlot={<CategoryPickerField value={categoryId} onChange={setCategoryId} />}
131
+ category={categoryId === null ? "" : String(categoryId)}
132
+ onCategoryChange={(id) => setCategoryId(id === "" ? null : Number(id))}
133
+ renderCategoryPicker={({ value, setCategory }) => (
134
+ <CategoryPickerField
135
+ value={value === "" ? null : Number(value)}
136
+ onChange={(id) => setCategory(id === null ? "" : String(id))}
137
+ />
138
+ )}
90
139
  gallerySlot={<MediaGalleryField bag={gallery} />}
91
140
  />
92
141
  ```
93
142
 
143
+ The category is a **render prop**, not a node, because the composer's category
144
+ moves only through `setCategory` — a node rendered into a slot cannot reach it,
145
+ and a picker that cannot report what was chosen means `features` (the whole
146
+ point of the slot) is never read. `category` / `onCategoryChange` are there
147
+ because the container holds the id anyway: `useCategoryFeatures(id)` is keyed
148
+ by it.
149
+
150
+ The gallery is a node, but the BAG is the same object the composer got — that
151
+ is what makes `bag.refs` the value of `images_draft` and `bag.settled` the
152
+ publish gate. Two queues means the gate talks about photos it cannot see.
153
+
154
+ The schema arrives as three props, not one, so an empty list is never mistaken
155
+ for "this category asks nothing": `featuresLoading` and `featuresError` each
156
+ block the publish with their own sentence, and only a settled, empty schema
157
+ prints "no extra details for this category".
158
+
94
159
  The flow is `create draft → save into it → publish`. The composer always saves
95
160
  before it publishes, because `publish` promotes the STORED draft: publishing
96
161
  without saving would promote whatever was there before the last keystroke.
@@ -8,11 +8,26 @@
8
8
  * the substitution that cost the fleet an incident (spec §7.4).
9
9
  */
10
10
  import type { ReactElement } from "react";
11
+ import type { LinkComponent } from "@stapel/core";
11
12
  import type { ThemeModeProp } from "./types.js";
12
- export interface FavoritesPaneProps extends ThemeModeProp {
13
+ /** How a card in this grid opens — the same one-contract union `<ListingCard>`
14
+ * takes, one level up, so a pane cannot re-introduce the double navigation the
15
+ * card no longer allows. */
16
+ export type FavoritesPaneOpenProps = {
13
17
  /** Where a card leads. The container owns routing. */
14
- readonly hrefFor?: (id: number) => string;
15
- readonly onOpen?: (id: number) => void;
16
- }
18
+ readonly hrefFor: (id: number) => string;
19
+ /** The host's `<Link>`, so a click stays inside the SPA. */
20
+ readonly linkComponent?: LinkComponent;
21
+ readonly onOpen?: undefined;
22
+ } | {
23
+ readonly onOpen: (id: number) => void;
24
+ readonly hrefFor?: undefined;
25
+ readonly linkComponent?: undefined;
26
+ } | {
27
+ readonly hrefFor?: undefined;
28
+ readonly onOpen?: undefined;
29
+ readonly linkComponent?: undefined;
30
+ };
31
+ export type FavoritesPaneProps = ThemeModeProp & FavoritesPaneOpenProps;
17
32
  export declare function FavoritesPane(props: FavoritesPaneProps): ReactElement;
18
33
  //# sourceMappingURL=FavoritesPane.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FavoritesPane.d.ts","sourceRoot":"","sources":["../../src/default/FavoritesPane.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAQ1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,sDAAsD;IACtD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,YAAY,CAiGrE"}
1
+ {"version":3,"file":"FavoritesPane.d.ts","sourceRoot":"","sources":["../../src/default/FavoritesPane.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAOlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;4BAE4B;AAC5B,MAAM,MAAM,sBAAsB,GAC9B;IACE,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B,GACD;IACE,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;CACpC,GACD;IACE,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;CACpC,CAAC;AAEN,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,sBAAsB,CAAC;AAiBxE,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,YAAY,CA2FrE"}
@@ -6,6 +6,18 @@ import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
6
6
  import { ErrorAlert } from "./ErrorAlert.js";
7
7
  import { ListingCard } from "./ListingCard.js";
8
8
  import { ListingsSkinTheme } from "./theme.js";
9
+ /** The card's own open props for one row. One arm, never two. */
10
+ function cardOpenProps(props, id) {
11
+ if (props.hrefFor !== undefined) {
12
+ const href = props.hrefFor(id);
13
+ return props.linkComponent !== undefined
14
+ ? { href, linkComponent: props.linkComponent }
15
+ : { href };
16
+ }
17
+ if (props.onOpen !== undefined)
18
+ return { onOpen: props.onOpen };
19
+ return {};
20
+ }
9
21
  export function FavoritesPane(props) {
10
22
  const t = useT();
11
23
  const describe = useDescribeFlowError();
@@ -20,9 +32,7 @@ export function FavoritesPane(props) {
20
32
  language: undefined,
21
33
  }), action: _jsx(Button, { size: "small", "data-analytics": "none", "data-analytics-reason": "retrying a read the person already asked for; not a business action", onClick: bag.refetch, children: t(LISTINGS_I18N_KEYS.mineRetry) }) })),
22
34
  empty: () => (_jsx(Empty, { "data-testid": "listings-favorites-empty", description: t(LISTINGS_I18N_KEYS.favoritesEmpty) })),
23
- ready: (rows) => (_jsx(Flex, { wrap: true, gap: 16, "data-testid": "listings-favorites-grid", children: rows.map((row) => (_jsx("div", { style: { width: 240 }, children: _jsx(ListingCard, { listing: row, ...(props.hrefFor !== undefined
24
- ? { href: props.hrefFor(row.id) }
25
- : {}), ...(props.onOpen !== undefined ? { onOpen: props.onOpen } : {}) }) }, row.id))) })),
35
+ ready: (rows) => (_jsx(Flex, { wrap: true, gap: 16, "data-testid": "listings-favorites-grid", children: rows.map((row) => (_jsx("div", { style: { width: 240 }, children: _jsx(ListingCard, { listing: row, ...cardOpenProps(props, row.id) }) }, row.id))) })),
26
36
  }), _jsxs(Space, { children: [_jsx(Button, { size: "small", disabled: !bag.prevPage.available, "data-testid": "listings-favorites-prev", "data-analytics": "none", "data-analytics-reason": "paging a list the person is already reading; not a business action", onClick: bag.goPrev, children: t(LISTINGS_I18N_KEYS.pagePrev) }), _jsx(Button, { size: "small", disabled: !bag.nextPage.available, "data-testid": "listings-favorites-next", "data-analytics": "none", "data-analytics-reason": "paging a list the person is already reading; not a business action", onClick: bag.goNext, children: t(LISTINGS_I18N_KEYS.pageNext) })] })] }) }));
27
37
  }
28
38
  //# sourceMappingURL=FavoritesPane.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FavoritesPane.js","sourceRoot":"","sources":["../../src/default/FavoritesPane.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FavoritesPane.js","sourceRoot":"","sources":["../../src/default/FavoritesPane.tsx"],"names":[],"mappings":"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"}
@@ -4,7 +4,15 @@
4
4
  * `@stapel/search-react` takes a `renderCard` slot and the container fills it
5
5
  * with this (spec §3.7 / §6.2 item 1). The two pairs never import each other;
6
6
  * the CONTAINER is the seam, which is why this component takes a plain card
7
- * row and a plain `href`/`onOpen` rather than reaching for a router.
7
+ * row and a plain `href` rather than reaching for a router.
8
+ *
9
+ * ── One click, one navigation ──────────────────────────────────────────────
10
+ *
11
+ * `href` and `onOpen` used to be two optional props, and a card given both
12
+ * navigated TWICE: the handler ran, and the browser then followed the anchor
13
+ * that was still on the button. They are now three arms of a union — link,
14
+ * button, or neither — and `linkComponent` rides on the link arm so a
15
+ * container can hand in its router's `<Link>` and keep the anchor.
8
16
  *
9
17
  * ── What it renders without asking the server anything else ────────────────
10
18
  *
@@ -16,21 +24,55 @@
16
24
  *
17
25
  * ── The heart is never hidden ──────────────────────────────────────────────
18
26
  *
19
- * A visitor sees it, blocked, with the reason and the sign-in CTA the
20
- * container attaches (`?next=`). Hiding it would teach nobody that
21
- * favourites exist (private-space canon §6.3, spec §6.2 item 6).
27
+ * A visitor sees it, blocked, with the reason IN WORDS beside it and the
28
+ * sign-in link the container supplies (`signIn`, typically `?next=<current>`).
29
+ * Hiding it would teach nobody that favourites exist (private-space canon
30
+ * §6.3, spec §6.2 item 6) — and until 0.3.0 the reason lived only in a tooltip
31
+ * on a disabled button, which receives no pointer events in any browser: a
32
+ * reason nobody could read, and no door to walk through.
22
33
  */
23
34
  import type { ReactElement, ReactNode } from "react";
35
+ import type { LinkComponent, SignInCtaProp } from "@stapel/core";
24
36
  import type { ListingCard as ListingCardData } from "../api/types.js";
25
37
  import type { ThemeModeProp } from "./types.js";
26
- export interface ListingCardProps extends ThemeModeProp {
38
+ /**
39
+ * How the card opens — ONE of three, and the type says so.
40
+ *
41
+ * It used to be two optional props, and a caller that passed both got two
42
+ * navigations for one click: the handler ran and the browser then followed the
43
+ * anchor anyway. The storefront worked around it by passing `onOpen` only,
44
+ * which cost it a real anchor (no middle-click, no "open in new tab", nothing
45
+ * for a crawler to follow) on the most linkable element in the app.
46
+ *
47
+ * So the union has three arms and no fourth: a link, a button, or neither.
48
+ * `linkComponent` belongs to the link arm because it IS the link — handing a
49
+ * `<Link>` to a card that navigates by callback would be two answers to one
50
+ * question again.
51
+ */
52
+ export type ListingCardOpenProps = {
53
+ /** Where the card leads. A plain path — the pair never calls
54
+ * `window.location` and never builds a router descriptor. */
55
+ readonly href: string;
56
+ /** The host's `<Link>`, so the click stays inside the SPA. Absent: an
57
+ * antd link-button carrying the `href`, which reloads the page in a
58
+ * router app — correct, just not fast. */
59
+ readonly linkComponent?: LinkComponent;
60
+ readonly onOpen?: undefined;
61
+ } | {
62
+ /** The card opens by callback: rendered as a button, with no `href` for
63
+ * the browser to follow after the handler has already navigated. */
64
+ readonly onOpen: (id: number) => void;
65
+ readonly href?: undefined;
66
+ readonly linkComponent?: undefined;
67
+ } | {
68
+ /** No open control at all — a card inside a screen that is already the
69
+ * listing. */
70
+ readonly href?: undefined;
71
+ readonly onOpen?: undefined;
72
+ readonly linkComponent?: undefined;
73
+ };
74
+ export interface ListingCardBaseProps extends ThemeModeProp, SignInCtaProp {
27
75
  readonly listing: ListingCardData;
28
- /** Where the card leads. The container owns routing, so this is a plain
29
- * href a `<Link>` or an `<a>` can carry — the pair never calls
30
- * `window.location`. */
31
- readonly href?: string;
32
- /** Called instead of following `href`, for a host with its own navigation. */
33
- readonly onOpen?: (id: number) => void;
34
76
  /** Extra chrome the container adds (a `promoted` tag from search, say —
35
77
  * DSA Art. 26 marking belongs to the pair that receives it). */
36
78
  readonly badge?: ReactNode;
@@ -38,5 +80,6 @@ export interface ListingCardProps extends ThemeModeProp {
38
80
  * sense (the owner's own dashboard). NOT a way to hide it from visitors. */
39
81
  readonly showFavorite?: boolean;
40
82
  }
83
+ export type ListingCardProps = ListingCardBaseProps & ListingCardOpenProps;
41
84
  export declare function ListingCard(props: ListingCardProps): ReactElement;
42
85
  //# sourceMappingURL=ListingCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ListingCard.d.ts","sourceRoot":"","sources":["../../src/default/ListingCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIrD,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAQtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC;;4BAEwB;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;oEACgE;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B;gFAC4E;IAC5E,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,CA6HjE"}
1
+ {"version":3,"file":"ListingCard.d.ts","sourceRoot":"","sources":["../../src/default/ListingCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGrD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAStE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE;iEAC6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;8CAE0C;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B,GACD;IACE;wEACoE;IACpE,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;CACpC,GACD;IACE;kBACc;IACd,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;CACpC,CAAC;AAEN,MAAM,WAAW,oBAAqB,SAAQ,aAAa,EAAE,aAAa;IACxE,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC;oEACgE;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B;gFAC4E;IAC5E,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAmE3E,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAgIjE"}
@@ -1,14 +1,42 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, Card, Flex, Tooltip, Typography } from "antd";
3
- import { useT } from "@stapel/core";
3
+ import { useActionGate, useT } from "@stapel/core";
4
4
  import { FeatureBadges } from "@stapel/attributes-react/default";
5
5
  import { asFeatureDaoList, featuresDtoFromDaoList, featuresFromDaoList } from "../model/features.js";
6
6
  import { lifecycleCaption } from "../model/status.js";
7
7
  import { useFavoriteToggle } from "../headless/Favorites.js";
8
8
  import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
9
9
  import { HeartIcon } from "./icons.js";
10
+ import { SignInLink } from "./SignInLink.js";
10
11
  import { ListingPhoto } from "./ListingPhoto.js";
11
12
  import { ListingsSkinTheme } from "./theme.js";
13
+ /**
14
+ * The one control that opens the card: an anchor, a button, or nothing.
15
+ *
16
+ * Exactly one of the three renders, so exactly one navigation happens per
17
+ * click. That is the whole fix — the branch below has no arm in which both a
18
+ * handler and an `href` reach the DOM.
19
+ */
20
+ function OpenControl(props) {
21
+ const t = useT();
22
+ const label = t(LISTINGS_I18N_KEYS.cardOpen);
23
+ if (props.href !== undefined) {
24
+ const Link = props.linkComponent;
25
+ // The host's component is rendered as it comes: this pair has no CSS and
26
+ // no way to hand antd's button styling to a foreign element, and a wrapper
27
+ // element around a link is a click target that is not the link. A host
28
+ // that wants the antd look styles its own `<Link>` — it is one component,
29
+ // written once, and it is already the thing that knows the design system.
30
+ return Link !== undefined ? (_jsx(Link, { href: props.href, "aria-label": label, "data-testid": "listings-card-open", "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", children: label })) : (_jsx(Button, { size: "small", type: "link", href: props.href, "data-testid": "listings-card-open", "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", children: label }));
31
+ }
32
+ if (props.onOpen !== undefined) {
33
+ const onOpen = props.onOpen;
34
+ return (_jsx(Button, { size: "small", type: "link", "data-testid": "listings-card-open", "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", onClick: () => {
35
+ onOpen(props.listingId);
36
+ }, children: label }));
37
+ }
38
+ return null;
39
+ }
12
40
  export function ListingCard(props) {
13
41
  const t = useT();
14
42
  const { listing } = props;
@@ -21,6 +49,7 @@ export function ListingCard(props) {
21
49
  const price = listing.price !== undefined && listing.price.length > 0
22
50
  ? `${listing.price} ${listing.currency ?? ""}`.trim()
23
51
  : t(LISTINGS_I18N_KEYS.cardPriceAbsent);
52
+ const favoriteGate = useActionGate(favorite.gate);
24
53
  const favoriteLabel = t(favorite.favorited
25
54
  ? LISTINGS_I18N_KEYS.cardFavoriteRemove
26
55
  : LISTINGS_I18N_KEYS.cardFavoriteAdd);
@@ -28,10 +57,8 @@ export function ListingCard(props) {
28
57
  ? { "data-listing-status": status.status }
29
58
  : {}), cover: _jsx(ListingPhoto, { imageRef: listing.images?.[0], alt: listing.title ?? String(listing.id), style: { aspectRatio: "4 / 3" } }), children: _jsxs(Flex, { vertical: true, gap: 4, children: [props.badge, _jsx(Typography.Text, { strong: true, "data-testid": "listings-card-price", children: price }), _jsx(Typography.Text, { ellipsis: true, "data-testid": "listings-card-title", children: listing.title ?? "" }), titleDaos.length > 0 ? (_jsx(Typography.Text, { type: "secondary", ellipsis: true, children: _jsx(FeatureBadges, { features: featuresFromDaoList(titleDaos)
30
59
  .map((view) => view.feature), values: featuresDtoFromDaoList(titleDaos) }) })) : null, badgeFeatures.length > 0 ? (_jsx(FeatureBadges, { features: badgeFeatures.map((view) => view.feature), values: badgeValues })) : null, listing.location_label !== undefined &&
31
- listing.location_label.length > 0 ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "listings-card-location", children: listing.location_label })) : null, _jsxs(Flex, { gap: 8, align: "center", children: [props.href !== undefined || props.onOpen !== undefined ? (_jsx(Button, { size: "small", type: "link", ...(props.href !== undefined ? { href: props.href } : {}), "data-testid": "listings-card-open", "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", onClick: () => {
32
- props.onOpen?.(listing.id);
33
- }, children: t(LISTINGS_I18N_KEYS.cardOpen) })) : null, props.showFavorite === false ? null : (_jsx(Tooltip, { title: favorite.gate.available
60
+ listing.location_label.length > 0 ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "listings-card-location", children: listing.location_label })) : null, _jsxs(Flex, { gap: 8, align: "center", children: [_jsx(OpenControl, { ...props, listingId: listing.id }), props.showFavorite === false ? null : (_jsx(Tooltip, { title: favorite.gate.available
34
61
  ? favoriteLabel
35
- : t(favorite.gate.block.code, favorite.gate.block.params), children: _jsx("span", { "data-testid": "listings-card-favorite-wrap", children: _jsx(Button, { size: "small", disabled: !favorite.gate.available, "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: _jsx(HeartIcon, { filled: favorite.favorited }) }) }) }))] })] }) }) }));
62
+ : t(favorite.gate.block.code, favorite.gate.block.params), children: _jsx("span", { "data-testid": "listings-card-favorite-wrap", children: _jsx(Button, { size: "small", disabled: !favorite.gate.available, "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: _jsx(HeartIcon, { filled: favorite.favorited }) }) }) }))] }), props.showFavorite === false || favoriteGate.reason === undefined ? null : (_jsxs(Typography.Text, { type: "secondary", "data-testid": "listings-card-favorite-blocked", children: [favoriteGate.reason, _jsx(SignInLink, { cta: props.signIn, testId: "listings-card-sign-in" })] }))] }) }) }));
36
63
  }
37
64
  //# sourceMappingURL=ListingCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ListingCard.js","sourceRoot":"","sources":["../../src/default/ListingCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ListingCard.js","sourceRoot":"","sources":["../../src/default/ListingCard.tsx"],"names":[],"mappings":"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"}