@stapel/listings-react 0.12.0 → 0.14.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 +83 -0
- package/dist/default/ListingCard.d.ts +59 -3
- package/dist/default/ListingCard.d.ts.map +1 -1
- package/dist/default/ListingCard.js +79 -17
- package/dist/default/ListingCard.js.map +1 -1
- package/dist/default/ListingComposerPage.d.ts +108 -9
- package/dist/default/ListingComposerPage.d.ts.map +1 -1
- package/dist/default/ListingComposerPage.js +76 -151
- package/dist/default/ListingComposerPage.js.map +1 -1
- package/dist/default/ListingPhoto.d.ts +30 -0
- package/dist/default/ListingPhoto.d.ts.map +1 -1
- package/dist/default/ListingPhoto.js +33 -0
- package/dist/default/ListingPhoto.js.map +1 -1
- package/dist/default/ListingSerpCard.d.ts +11 -0
- package/dist/default/ListingSerpCard.d.ts.map +1 -1
- package/dist/default/ListingSerpCard.js +7 -15
- package/dist/default/ListingSerpCard.js.map +1 -1
- package/dist/default/index.d.ts +3 -3
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +6 -3
- package/dist/default/index.js.map +1 -1
- package/llms.txt +4 -3
- package/manifest.json +20 -1
- package/nav-manifest.json +1 -1
- package/package.json +2 -2
- package/src/analytics/generated/events.json +1 -1
- package/src/default/ListingCard.tsx +209 -110
- package/src/default/ListingComposerPage.tsx +73 -54
- package/src/default/ListingPhoto.tsx +64 -0
- package/src/default/ListingSerpCard.tsx +130 -135
- package/src/default/index.ts +13 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# @stapel/listings-react
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- The desktop result card is a list row, and it has the phone card's gallery
|
|
8
|
+
|
|
9
|
+
Measured on a live 1440px result page in "list" view: **one offer per screen**
|
|
10
|
+
— a 974×835 card of which the photograph was 974×731 — showing a SINGLE photo,
|
|
11
|
+
with no carousel, and that photo inside the card's own link. The phone card,
|
|
12
|
+
measured in the same run, was correct on every count: three photos per card,
|
|
13
|
+
dots on all of them, a 17px peek, and the strip a sibling of the anchor.
|
|
14
|
+
|
|
15
|
+
Two changes, both to `<ListingCard>`, and the second to `<ListingSerpCard>` as
|
|
16
|
+
well.
|
|
17
|
+
|
|
18
|
+
**The photos are a strip, outside the anchor.** `<ListingPhotoStrip>` is now
|
|
19
|
+
the one gallery both card surfaces draw: a `SkinCarousel` over every stored
|
|
20
|
+
photo, peeking and dotted above one photo, one slide for a listing with none.
|
|
21
|
+
It sits BESIDE the anchor rather than inside it, because a swipeable strip is a
|
|
22
|
+
control, a link may not contain one, and a horizontal swipe that ends inside an
|
|
23
|
+
`<a>` is a swipe the browser may deliver as a click — the defect that made the
|
|
24
|
+
desktop gallery unreachable. The "the card is the link" ruling is otherwise
|
|
25
|
+
untouched: the price, the title, the badges and the place are still inside one
|
|
26
|
+
real `<a href>` whose accessible name is the title.
|
|
27
|
+
|
|
28
|
+
**The card lays out as a row when it is wide enough to be one.** Above
|
|
29
|
+
`LISTING_CARD_ROW_MIN` (560px of the CARD's own inline size, asked with a
|
|
30
|
+
`@container` query and never a viewport breakpoint) the strip moves beside the
|
|
31
|
+
reading column at a fixed `LISTING_CARD_ROW_MEDIA` (260px), so several offers
|
|
32
|
+
fit a screen. In a grid the column is never that wide, so nothing about a grid
|
|
33
|
+
changes.
|
|
34
|
+
|
|
35
|
+
New from `@stapel/listings-react/default`: `ListingPhotoStrip`,
|
|
36
|
+
`LISTING_CARD_ROW_MIN` and `LISTING_CARD_ROW_MEDIA`. The frame's own class
|
|
37
|
+
names (`CARD_QUERY_CLASS`, `CARD_FRAME_CLASS`, `CARD_MEDIA_CLASS`,
|
|
38
|
+
`CARD_MAIN_CLASS`, `CARD_BLEED_CLASS`) stay on the module, beside
|
|
39
|
+
`cardTargetCss`, where the other card surfaces read them.
|
|
40
|
+
|
|
41
|
+
Demos photograph both cards at both widths — a 320px grid cell and a 960px list
|
|
42
|
+
row — and the suite asserts the structure the layout acts on plus the rule text
|
|
43
|
+
a browser applies.
|
|
44
|
+
|
|
45
|
+
## 0.13.0
|
|
46
|
+
|
|
47
|
+
### Minor Changes
|
|
48
|
+
|
|
49
|
+
- 5397813: The composer asks what is being sold before it asks about the parcel
|
|
50
|
+
|
|
51
|
+
`<ListingComposerPage>` had two section orders chosen by the form's width, and
|
|
52
|
+
the narrow one put the category's characteristics directly under the category
|
|
53
|
+
picker. On a leaf with a handful of attributes that was an improvement; on an
|
|
54
|
+
imported one it was a funnel with nothing left in it. Measured at 390x844 on a
|
|
55
|
+
live classified deployment: 32 fields between the category and the title, so
|
|
56
|
+
`Title` sat at y=5575, `Price` at y=5871 and `Photos` at y=6245 of a 7308px
|
|
57
|
+
form — the seller was asked for the parcel's weight, its length and "what the
|
|
58
|
+
goods are measured in" before being asked what the thing is or what it costs.
|
|
59
|
+
|
|
60
|
+
There is now ONE order, at every width, and it belongs to the component:
|
|
61
|
+
|
|
62
|
+
category → title → description → price → currency → where → photos →
|
|
63
|
+
the category's characteristics → the listing's own options
|
|
64
|
+
|
|
65
|
+
Measured on the showcase's own composer story at 390 (five attribute rows, not
|
|
66
|
+
32): title moves 993 → 314, the first attribute row 412 → 1266, and the page
|
|
67
|
+
shortens 2227 → 2071px.
|
|
68
|
+
|
|
69
|
+
- `COMPOSER_STACKED_BELOW` is gone, and with it the width measurement that
|
|
70
|
+
chose between the two orders. `COMPOSER_DETAILS_PLACEMENT` replaces it: a
|
|
71
|
+
constant, exported because `data-placement` on the characteristics region is
|
|
72
|
+
what an e2e suite reads to prove the order has not regressed.
|
|
73
|
+
- The discoverability the narrow order was reaching for is kept by the two
|
|
74
|
+
things that do not move the questions around — "take me to the first empty
|
|
75
|
+
field", which now also OPENS whatever disclosure the field is folded inside,
|
|
76
|
+
and a shorter region.
|
|
77
|
+
- `<FeatureFields>` takes `groupCollapse` (`"none"` by default, unchanged for
|
|
78
|
+
every existing host; `"auto"` in the composer). Under `"auto"` each named
|
|
79
|
+
group is a native `<details>` that starts open when it asks something
|
|
80
|
+
required or something already answered, and closed otherwise — so identity
|
|
81
|
+
groups are open and the delivery dimensions and wholesale terms are one tap
|
|
82
|
+
away under their own headings. The rule reads the SCHEMA and never a list of
|
|
83
|
+
group names: groups are admin-authored text in the deployment's language.
|
|
84
|
+
- The group order the catalogue emits is untouched.
|
|
85
|
+
|
|
3
86
|
## 0.12.0
|
|
4
87
|
|
|
5
88
|
### Minor Changes
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* Nothing on a classified works that way: the card IS the target, and the only
|
|
17
17
|
* separate control on it is the favourite heart.
|
|
18
18
|
*
|
|
19
|
-
* So the
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* So the price, the title, the badges and the location live INSIDE one anchor
|
|
20
|
+
* that covers the whole card, and `listings.card.open` is retired rather than
|
|
21
|
+
* left orphaned in three catalogues.
|
|
22
22
|
*
|
|
23
23
|
* What that must not cost is the anchor semantics won earlier: this is a real
|
|
24
24
|
* `<a href>`, so middle-click opens a tab, ⌘-click opens a tab, "copy link
|
|
@@ -44,6 +44,29 @@
|
|
|
44
44
|
* A listing with no title falls back to `listings.card.untitled`, because a
|
|
45
45
|
* link announced as nothing is worse than one announced as untitled.
|
|
46
46
|
*
|
|
47
|
+
* ── The photos moved OUT of the anchor, and the card became a ROW ─────────
|
|
48
|
+
*
|
|
49
|
+
* Both from the same measurement of the live desktop SERP, in list view:
|
|
50
|
+
* **one card per screen** — 974×835, of which the photograph was 974×731 —
|
|
51
|
+
* showing a SINGLE photo, with no carousel, and that photo inside the anchor.
|
|
52
|
+
* The phone card beside it was correct on every count.
|
|
53
|
+
*
|
|
54
|
+
* The photo used to be inside the anchor on the argument that a still `<img>`
|
|
55
|
+
* in a link is just a bigger link, which is true and stops being true the
|
|
56
|
+
* moment there is more than one photo: a swipeable strip is a control, a link
|
|
57
|
+
* may not contain one, and a horizontal swipe that ends inside an `<a>` is a
|
|
58
|
+
* swipe the browser may deliver as a click. So the strip is a SIBLING of the
|
|
59
|
+
* anchor — the arrangement `<ListingSerpCard>` has always used, and the reason
|
|
60
|
+
* the phone gallery works — and the anchor still covers everything a person
|
|
61
|
+
* READS. `<ListingPhotoStrip>` is the one gallery both cards draw.
|
|
62
|
+
*
|
|
63
|
+
* The row is the other half. A grid card handed a full-page-wide track keeps
|
|
64
|
+
* its shape and becomes a banner; this card now asks its OWN width
|
|
65
|
+
* (`@container`, see {@link LISTING_CARD_ROW_MIN}) and lays the photo beside
|
|
66
|
+
* the text above 560px. In a grid its column is never that wide, so nothing
|
|
67
|
+
* about the grid changes; in a list several rows fit a screen, which is the
|
|
68
|
+
* only reason a list exists.
|
|
69
|
+
*
|
|
47
70
|
* ── Why the heart is a row under the card and not floating on the photo ────
|
|
48
71
|
*
|
|
49
72
|
* Because for a signed-out visitor — which is most of the traffic a storefront
|
|
@@ -131,8 +154,41 @@ export type ListingCardOpenProps = {
|
|
|
131
154
|
export type ListingCardBlockedReason = "text" | "line";
|
|
132
155
|
/** The class the whole-card target carries, for {@link cardTargetCss}. */
|
|
133
156
|
export declare const CARD_TARGET_CLASS = "stapel-listing-card-target";
|
|
157
|
+
/** The class the card's OUTER box carries — the size container the row layout
|
|
158
|
+
* is asked about. A container cannot answer a query about itself, which is why
|
|
159
|
+
* this is a wrapper and not the frame. */
|
|
160
|
+
export declare const CARD_QUERY_CLASS = "stapel-listing-card-q";
|
|
161
|
+
/** The class the media/reading frame carries. */
|
|
162
|
+
export declare const CARD_FRAME_CLASS = "stapel-listing-card-frame";
|
|
163
|
+
/** The class the photo strip's box carries. */
|
|
164
|
+
export declare const CARD_MEDIA_CLASS = "stapel-listing-card-media";
|
|
165
|
+
/** The class the reading column carries. */
|
|
166
|
+
export declare const CARD_MAIN_CLASS = "stapel-listing-card-main";
|
|
167
|
+
/** Added to the frame by a card whose media is FULL-BLEED when stacked
|
|
168
|
+
* (`<ListingCard>`), so the row arm gets the inset the stacked arm does not. */
|
|
169
|
+
export declare const CARD_BLEED_CLASS = "stapel-listing-card-bleed";
|
|
134
170
|
/** The `href` the hoisted card stylesheet is deduplicated by. */
|
|
135
171
|
export declare const CARD_TARGET_STYLE_HREF = "stapel-listings-card-target";
|
|
172
|
+
/**
|
|
173
|
+
* The card's own inline size above which it stops being a card and becomes a
|
|
174
|
+
* ROW.
|
|
175
|
+
*
|
|
176
|
+
* Measured on the live desktop SERP in "list" view: **one card per screen**,
|
|
177
|
+
* 974×835, of which the photograph was 974×731 — a grid card handed a
|
|
178
|
+
* full-page track and asked to keep its shape. A shopper comparing offers got
|
|
179
|
+
* one offer per scroll.
|
|
180
|
+
*
|
|
181
|
+
* 560px is the width at which a 4:3 photo stops being a banner: below it a
|
|
182
|
+
* two-column row leaves the text nothing, above it the photo is a thumbnail
|
|
183
|
+
* beside a paragraph. A `@container` query rather than a media query because
|
|
184
|
+
* the card does not know the viewport and must not care: the same card is a
|
|
185
|
+
* grid cell 300px wide on a 1440px screen and a full-width row on a 700px one,
|
|
186
|
+
* and only its OWN width decides which it is.
|
|
187
|
+
*/
|
|
188
|
+
export declare const LISTING_CARD_ROW_MIN = 560;
|
|
189
|
+
/** The photo's width in the row arm. Wide enough to read the goods, narrow
|
|
190
|
+
* enough that several rows fit a screen — the whole point of a list. */
|
|
191
|
+
export declare const LISTING_CARD_ROW_MEDIA = 260;
|
|
136
192
|
/**
|
|
137
193
|
* The one rule an inline style cannot express: `:focus-visible`.
|
|
138
194
|
*
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button, Card, Flex, Typography, theme as antdTheme } from "antd";
|
|
3
3
|
import { GatedControl, SkinTheme } from "@stapel/tokens-antd/skin";
|
|
4
4
|
import { useActionGate, useT } from "@stapel/core";
|
|
@@ -10,12 +10,45 @@ import { useFavoriteToggle } from "../headless/Favorites.js";
|
|
|
10
10
|
import { LISTINGS_I18N_KEYS } from "../i18n/keys.js";
|
|
11
11
|
import { HeartIcon } from "./icons.js";
|
|
12
12
|
import { SignInLink } from "./SignInLink.js";
|
|
13
|
-
import {
|
|
13
|
+
import { ListingPhotoStrip } from "./ListingPhoto.js";
|
|
14
14
|
import { ListingPrice } from "./ListingPrice.js";
|
|
15
15
|
/** The class the whole-card target carries, for {@link cardTargetCss}. */
|
|
16
16
|
export const CARD_TARGET_CLASS = "stapel-listing-card-target";
|
|
17
|
+
/** The class the card's OUTER box carries — the size container the row layout
|
|
18
|
+
* is asked about. A container cannot answer a query about itself, which is why
|
|
19
|
+
* this is a wrapper and not the frame. */
|
|
20
|
+
export const CARD_QUERY_CLASS = "stapel-listing-card-q";
|
|
21
|
+
/** The class the media/reading frame carries. */
|
|
22
|
+
export const CARD_FRAME_CLASS = "stapel-listing-card-frame";
|
|
23
|
+
/** The class the photo strip's box carries. */
|
|
24
|
+
export const CARD_MEDIA_CLASS = "stapel-listing-card-media";
|
|
25
|
+
/** The class the reading column carries. */
|
|
26
|
+
export const CARD_MAIN_CLASS = "stapel-listing-card-main";
|
|
27
|
+
/** Added to the frame by a card whose media is FULL-BLEED when stacked
|
|
28
|
+
* (`<ListingCard>`), so the row arm gets the inset the stacked arm does not. */
|
|
29
|
+
export const CARD_BLEED_CLASS = "stapel-listing-card-bleed";
|
|
17
30
|
/** The `href` the hoisted card stylesheet is deduplicated by. */
|
|
18
31
|
export const CARD_TARGET_STYLE_HREF = "stapel-listings-card-target";
|
|
32
|
+
/**
|
|
33
|
+
* The card's own inline size above which it stops being a card and becomes a
|
|
34
|
+
* ROW.
|
|
35
|
+
*
|
|
36
|
+
* Measured on the live desktop SERP in "list" view: **one card per screen**,
|
|
37
|
+
* 974×835, of which the photograph was 974×731 — a grid card handed a
|
|
38
|
+
* full-page track and asked to keep its shape. A shopper comparing offers got
|
|
39
|
+
* one offer per scroll.
|
|
40
|
+
*
|
|
41
|
+
* 560px is the width at which a 4:3 photo stops being a banner: below it a
|
|
42
|
+
* two-column row leaves the text nothing, above it the photo is a thumbnail
|
|
43
|
+
* beside a paragraph. A `@container` query rather than a media query because
|
|
44
|
+
* the card does not know the viewport and must not care: the same card is a
|
|
45
|
+
* grid cell 300px wide on a 1440px screen and a full-width row on a 700px one,
|
|
46
|
+
* and only its OWN width decides which it is.
|
|
47
|
+
*/
|
|
48
|
+
export const LISTING_CARD_ROW_MIN = 560;
|
|
49
|
+
/** The photo's width in the row arm. Wide enough to read the goods, narrow
|
|
50
|
+
* enough that several rows fit a screen — the whole point of a list. */
|
|
51
|
+
export const LISTING_CARD_ROW_MEDIA = 260;
|
|
19
52
|
/**
|
|
20
53
|
* The one rule an inline style cannot express: `:focus-visible`.
|
|
21
54
|
*
|
|
@@ -29,9 +62,33 @@ export const CARD_TARGET_STYLE_HREF = "stapel-listings-card-target";
|
|
|
29
62
|
* design system's ROLE catalogue and this is a component's private plumbing.
|
|
30
63
|
*/
|
|
31
64
|
export function cardTargetCss() {
|
|
65
|
+
const q = `.${CARD_QUERY_CLASS}`;
|
|
66
|
+
const frame = `.${CARD_FRAME_CLASS}`;
|
|
67
|
+
const media = `.${CARD_MEDIA_CLASS}`;
|
|
68
|
+
const main = `.${CARD_MAIN_CLASS}`;
|
|
69
|
+
const bleed = `.${CARD_BLEED_CLASS}`;
|
|
32
70
|
return [
|
|
33
71
|
`.${CARD_TARGET_CLASS}{display:block;color:inherit;text-decoration:none}`,
|
|
34
72
|
`.${CARD_TARGET_CLASS}:focus-visible{outline:2px solid var(--listing-card-focus);outline-offset:2px}`,
|
|
73
|
+
// The card asks about its OWN width, not the window's — see
|
|
74
|
+
// `LISTING_CARD_ROW_MIN`.
|
|
75
|
+
`${q}{container-type:inline-size}`,
|
|
76
|
+
`${frame}{display:flex;flex-direction:column;min-inline-size:0}`,
|
|
77
|
+
`${media}{min-inline-size:0}`,
|
|
78
|
+
`${main}{display:flex;flex-direction:column;flex:1 1 auto;min-inline-size:0}`,
|
|
79
|
+
// The row arm. `align-items:flex-start` so a short text column does not
|
|
80
|
+
// stretch the photo, and a fixed media basis so the picture cannot grow
|
|
81
|
+
// into the 974px banner the live SERP was measured at.
|
|
82
|
+
`@container (min-width:${String(LISTING_CARD_ROW_MIN)}px){` +
|
|
83
|
+
`${frame}{flex-direction:row;align-items:flex-start}` +
|
|
84
|
+
`${media}{flex:0 0 ${String(LISTING_CARD_ROW_MEDIA)}px;` +
|
|
85
|
+
`max-inline-size:${String(LISTING_CARD_ROW_MEDIA)}px}` +
|
|
86
|
+
// A full-bleed stacked card has no padding of its own to give the row
|
|
87
|
+
// arm, so the row arm states its own; the reading column's padding is
|
|
88
|
+
// what separates the two.
|
|
89
|
+
`${bleed}{padding-block:var(--listing-card-inset);` +
|
|
90
|
+
`padding-inline-start:var(--listing-card-inset)}` +
|
|
91
|
+
`}`,
|
|
35
92
|
].join("");
|
|
36
93
|
}
|
|
37
94
|
/** The anchor: a block that inherits the card's own type colour rather than
|
|
@@ -120,8 +177,9 @@ export function ListingCard(props) {
|
|
|
120
177
|
// as untitled.
|
|
121
178
|
const targetLabel = title.length > 0 ? title : t(LISTINGS_I18N_KEYS.cardUntitled);
|
|
122
179
|
/**
|
|
123
|
-
* Everything a person
|
|
124
|
-
*
|
|
180
|
+
* Everything a person READS on the card, in the order a classified reads it:
|
|
181
|
+
* price, title, the seller's own spec line, the badges, the place. The
|
|
182
|
+
* photos are not here — they are the strip beside this, outside the anchor.
|
|
125
183
|
*
|
|
126
184
|
* The search projection carries `title`, `price`, `currency`,
|
|
127
185
|
* `location_label`, `image` and `published_at` and NO feature badges — so
|
|
@@ -129,20 +187,24 @@ export function ListingCard(props) {
|
|
|
129
187
|
* all of them absent. That is why the photo and the price carry the layout:
|
|
130
188
|
* they are the two fields a result always has.
|
|
131
189
|
*/
|
|
132
|
-
const content = (_jsxs(
|
|
133
|
-
|
|
134
|
-
return (_jsxs(SkinTheme, { surface: "bare", ...(props.mode !== undefined ? { mode: props.mode } : {}), children: [_jsx("style", { href: CARD_TARGET_STYLE_HREF, precedence: "default", children: cardTargetCss() }),
|
|
190
|
+
const content = (_jsxs(Flex, { vertical: true, gap: spacing[1], style: { minWidth: 0, padding: token.paddingSM }, children: [props.badge, _jsx(Typography.Text, { strong: true, "data-testid": "listings-card-price", children: _jsx(ListingPrice, { amount: listing.price, ...(listing.currency !== undefined ? { currency: listing.currency } : {}) }) }), _jsx(Typography.Text, { ellipsis: true, "data-testid": "listings-card-title", children: title }), titleDaos.length > 0 ? (_jsx(Typography.Text, { type: "secondary", ellipsis: true, children: _jsx(FeatureBadges, { features: featuresFromDaoList(titleDaos, copy).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 &&
|
|
191
|
+
listing.location_label.length > 0 ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "listings-card-location", children: listing.location_label })) : null] }));
|
|
192
|
+
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
|
|
135
193
|
? { "data-listing-status": status.status }
|
|
136
194
|
: {}),
|
|
137
|
-
// The body's own padding is zero because the
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
// back, from the same token
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
:
|
|
195
|
+
// The body's own padding is zero because the frame fills the card and
|
|
196
|
+
// the photo runs edge to edge when it is stacked: padding here would
|
|
197
|
+
// be a strip of card around a picture. The text block inside the
|
|
198
|
+
// anchor carries the same padding back, from the same token, and the
|
|
199
|
+
// row arm states its own inset (`--listing-card-inset`).
|
|
200
|
+
styles: { body: { minWidth: 0, padding: 0 } }, style: {
|
|
201
|
+
["--listing-card-focus"]: token.colorPrimary,
|
|
202
|
+
["--listing-card-inset"]: `${String(token.paddingSM)}px`,
|
|
203
|
+
}, 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: {
|
|
204
|
+
paddingInline: token.paddingSM,
|
|
205
|
+
paddingBlockEnd: token.paddingSM,
|
|
206
|
+
}, 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, { disabled: bind.disabled, "data-disabled-reason": "the enclosing <GatedControl> renders the gate's reason beside this button", ...(bind["aria-describedby"] !== undefined
|
|
207
|
+
? { "aria-describedby": bind["aria-describedby"] }
|
|
208
|
+
: {}), "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 }) }) })) }), 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" }) }))] }))] })] }) }) })] }));
|
|
147
209
|
}
|
|
148
210
|
//# 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,3 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<ListingComposerPage>` — the submission screen.
|
|
3
|
+
*
|
|
4
|
+
* Five contracts meet on this page and each arrives through its own seam, so
|
|
5
|
+
* the component can be read top to bottom without knowing any of the other
|
|
6
|
+
* pairs:
|
|
7
|
+
*
|
|
8
|
+
* category `renderCategoryPicker` — the container's `<CategoryPickerField>`,
|
|
9
|
+
* given `setCategory` to call
|
|
10
|
+
* where `locationPicker` — the container's `<LocationField>`
|
|
11
|
+
* (`@stapel/geo-react`)
|
|
12
|
+
* currency `renderCurrencyPicker` — whatever vocabulary the deployment sells in
|
|
13
|
+
* details `features` — the schema, drawn by `<FeatureFields>` (L0)
|
|
14
|
+
* photos `gallerySlot` — the container's `<MediaGalleryField bag>`,
|
|
15
|
+
* whose bag is handed here as `images`
|
|
16
|
+
* the draft this pair
|
|
17
|
+
*
|
|
18
|
+
* ── An unfilled slot is NAMED, never improvised ────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* Every one of those four slots used to degrade into a control this pair could
|
|
21
|
+
* write on its own, and every one of those controls asked a question no seller
|
|
22
|
+
* can answer: a numeric category id typed into a text box, a currency CODE
|
|
23
|
+
* typed into a text box, and — the defect the owner named — two decimal boxes
|
|
24
|
+
* labelled Latitude and Longitude. A gallery heading with nothing under it was
|
|
25
|
+
* the same defect with the improvisation left out.
|
|
26
|
+
*
|
|
27
|
+
* So an unfilled slot renders `<SlotPlaceholder name="…"/>`: a labelled dashed
|
|
28
|
+
* region in a dev build, nothing at all in production. The rule is the one the
|
|
29
|
+
* rest of this codebase already lives by — `matchList`'s required empty arm,
|
|
30
|
+
* `ActionAvailability` with no "disabled for unknown reasons", `LoadState`
|
|
31
|
+
* refusing to fold failed into empty. A slot was the last place absence could
|
|
32
|
+
* still be silent, and `stapel/no-silent-slot` now says so at lint time.
|
|
33
|
+
*
|
|
34
|
+
* ── …and its LABEL goes with it ────────────────────────────────────────────
|
|
35
|
+
*
|
|
36
|
+
* The placeholder is nothing in production — but the `Form.Item` around it
|
|
37
|
+
* still drew its label, so a production composer with three unfilled slots
|
|
38
|
+
* rendered three labelled voids: "Category", "Currency", "Where it is" over
|
|
39
|
+
* empty space, and a "Photos" heading over air. A label is a promise that a
|
|
40
|
+
* control follows it. {@link SlotField} therefore renders the WHOLE field —
|
|
41
|
+
* label, help text and control — or nothing at all, and the `Photos` section
|
|
42
|
+
* does the same with its heading. `slotVisibility` pins the dev view on so a
|
|
43
|
+
* production-built showcase can still photograph the named placeholders.
|
|
44
|
+
*
|
|
45
|
+
* ── Why the pickers are slots and not dependencies ─────────────────────────
|
|
46
|
+
*
|
|
47
|
+
* A category tree, a geocoder and a currency vocabulary are all DEPLOYMENT
|
|
48
|
+
* knowledge, and all three live in sibling L2 pairs
|
|
49
|
+
* (`@stapel/categories-react`, `@stapel/geo-react`, the host's own). L2 pairs
|
|
50
|
+
* do not import each other; the container is the seam. A library that picked
|
|
51
|
+
* one would pick it for every host.
|
|
52
|
+
*
|
|
53
|
+
* ── The SECTION ORDER is this component's, and it is not a prop ────────────
|
|
54
|
+
*
|
|
55
|
+
* One order, at every width:
|
|
56
|
+
*
|
|
57
|
+
* category → title → description → price → currency → where → photos →
|
|
58
|
+
* the category's characteristics → the listing's own options
|
|
59
|
+
*
|
|
60
|
+
* The category comes first because everything after it depends on the choice.
|
|
61
|
+
* Then the five questions a seller came to answer — what it is, what it says,
|
|
62
|
+
* what it costs, where it is, what it looks like — and only then the questions
|
|
63
|
+
* the CATEGORY asks.
|
|
64
|
+
*
|
|
65
|
+
* This used to be two orders chosen by the form's width, and the narrow one
|
|
66
|
+
* put the characteristics directly under the category. The reason was real:
|
|
67
|
+
* measured on a live classified deployment, Mobile phones put the first
|
|
68
|
+
* attribute control at y=1596, two viewports below the fold, while the footer
|
|
69
|
+
* said "10 required details not filled in" with none of them on screen. The
|
|
70
|
+
* fix was not: the same leaf carries 32 imported fields, so on the next
|
|
71
|
+
* measurement of the same form the seller was asked for the parcel's weight,
|
|
72
|
+
* its length and "what the goods are measured in" BEFORE the title — title
|
|
73
|
+
* y=5575, price y=5871, photos y=6245, of a 7308px form. A composer that asks
|
|
74
|
+
* about postage before it asks what is being sold has no funnel left.
|
|
75
|
+
*
|
|
76
|
+
* So the discoverability problem is solved where it actually lives, and by two
|
|
77
|
+
* things that do not move the questions around:
|
|
78
|
+
*
|
|
79
|
+
* - the count of unfilled details is a CONTROL that goes to the first of them
|
|
80
|
+
* (`listings-composer-goto-missing`, below), so "10 required details" is
|
|
81
|
+
* never a dead end wherever the region sits;
|
|
82
|
+
* - the region itself is short, because `groupCollapse="auto"` opens the
|
|
83
|
+
* groups that ask something required and leaves the plumbing (delivery
|
|
84
|
+
* dimensions, wholesale terms) closed under its own heading.
|
|
85
|
+
*
|
|
86
|
+
* `detailsPlacement` is therefore gone as a decision — `data-placement` stays
|
|
87
|
+
* on the region as a constant, because an e2e that measured the regression
|
|
88
|
+
* needs something to read that is not a pixel count. A host that wants a
|
|
89
|
+
* different order does not want this page; it wants `<FeatureFields>` and its
|
|
90
|
+
* own form, which is exactly why that component is exported separately.
|
|
91
|
+
*
|
|
92
|
+
* ── Every blocked control says which of six reasons it is ──────────────────
|
|
93
|
+
*
|
|
94
|
+
* The publish button is the most-gated control in the fleet, and that is the
|
|
95
|
+
* point: "sign in", "choose a category", "we could not load what this
|
|
96
|
+
* category asks for", "this build cannot draw one of these details", "wait
|
|
97
|
+
* for the photos", "fix the highlighted fields" are six different problems
|
|
98
|
+
* with six different next actions. `firstBlock` orders them the way a person
|
|
99
|
+
* would be told, and `<GatedButton>` renders the reason beside the button —
|
|
100
|
+
* never a grey rectangle, never a hover.
|
|
101
|
+
*/
|
|
1
102
|
import type { ComponentType, ReactElement, ReactNode } from "react";
|
|
2
103
|
import type { FeatureDef } from "@stapel/attributes-react";
|
|
3
104
|
import type { ListingLocation } from "../model/draft.js";
|
|
@@ -10,16 +111,14 @@ import type { ThemeModeProp } from "./types.js";
|
|
|
10
111
|
*/
|
|
11
112
|
export declare const COMPOSER_MEASURE = "44rem";
|
|
12
113
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* A composer drawn in a 360px settings pane on a 1440px desktop is narrow; a
|
|
20
|
-
* viewport query would call it wide and bury its questions.
|
|
114
|
+
* What `data-placement` says on the characteristics region.
|
|
115
|
+
*
|
|
116
|
+
* A constant, and deliberately so: the order is the composer's and does not
|
|
117
|
+
* depend on the width any more (see the header). The attribute stays because
|
|
118
|
+
* an e2e suite measured the regression through it and needs a reading that is
|
|
119
|
+
* not a pixel count.
|
|
21
120
|
*/
|
|
22
|
-
export declare const
|
|
121
|
+
export declare const COMPOSER_DETAILS_PLACEMENT = "after-core-fields";
|
|
23
122
|
/**
|
|
24
123
|
* The DOM id of the control that answers one of the composer's own fields —
|
|
25
124
|
* the names `mirrorListingFields` refuses by (`title`, `description`, …).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListingComposerPage.d.ts","sourceRoot":"","sources":["../../src/default/ListingComposerPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ListingComposerPage.d.ts","sourceRoot":"","sources":["../../src/default/ListingComposerPage.tsx"],"names":[],"mappings":"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"}
|