Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

The Shop kit's listing — search, sizes and sorting, with a card of your own

ShopSearchPage(productPrefix: "/p/", showStockUnder: 5) { slot card { c => … } }

`ShopSearchPage` is the Shop kit's listing: every listed product as a card with its photograph, its price and how much is left, under a search box, a size filter and a sort order. A store that wants a different card fills the `card` slot and is handed a `ShopCard` with everything a card says already worked out — and keeps the search, the sizes and the sorting.

preview1 example compiled by CIuikitshop

Summary#

Mounted as it is, the listing is a storefront's front page:

[Route("/")] [AllowAnonymous]
component Front() { render { ShopSearchPage(); } }

Each product is a card: its cover photograph when it has one, its name, its price (and the price before a sale, struck through), and — once five or fewer are left — how many: "3 left", "Only one left", "Sold out".

Signature#

ShopSearchPage(string productPrefix = "/p/", bool linkById = false, bool onlySale = false, int showStockUnder = 5)
ShopSearchPage(…) { slot card { c => … } }        // c is a ShopCard

public class ShopCard {
  Product Product;   string Name;   string Href;
  string Photo;      string PhotoAlt;                // "" when the product has no photograph
  string Price;      string WasPrice;   string PriceNote;   // money as the shop writes it; WasPrice "" unless on sale
  int? Left;         bool SoldOut;      string Stock;       // Stock: "Sold out", "Only one left", "3 left" or ""
}

int? LeftOf(Variant[] variants);                              // null when it never runs out
string StockNote(Variant[] variants, int showUnder = 5);

Description#

What a card says#

The photograph is the product's cover — its first ProductImage — read aloud by the words staff gave it. The price is today's, in the shop's currency and its way of showing VAT. How much is left is the product's stock over all its variants; a product that never runs out (a gift card, a file) says nothing. showStockUnder is the count at which a card starts saying how many are left — 0 says only "Sold out".

A card of your own#

Fill the card slot and the listing draws your card for each product instead of its own. The search, the size filter, the sorting and the grid stay the kit's, so nothing has to be rebuilt to change how a product looks:

ShopSearchPage(showStockUnder: 3) {
  slot card { c =>
    Link(href: c.Href, label: c.Name) {
      Stack(gap: 2) {
        if (c.Photo != "") { Image(src: c.Photo, alt: c.PhotoAlt, aspectRatio: "1 / 1", objectFit: ObjectFit.Cover); }
        Text(c.Name, fontWeight: FontWeight.Bold);
        Text(c.WasPrice == "" ? c.Price : c.Price + " — was " + c.WasPrice);
        if (c.Stock != "") { Badge(c.Stock, tone: c.SoldOut ? Tone.Default : Tone.Warning); }
      }
    }
  }
}

c.Product is the product itself, for a field of your own subtype. The template is yours: it sees the page around it as any slot template does.

The sale shelf#

ShopSearchPage(onlySale: true) lists only what is marked down, and says so when nothing is.

Examples#

A pottery's front page with its own card#

app Pottery {
  use Osysharp.Ui;
  use Osysharp.Workflow;
  use Osysharp.Storage;
  use Osysharp.Shop@1 { egress "api.resend.com"; secret "ResendApiKey"; }
  use Osysharp.Identity@0;
  use Osysharp.UserAccounts@1;
  use Osysharp.Payments@0;
  use Osysharp.Shipping@0;
  model "model/**/*.osy";
}

using Osysharp.Ui;
using Osysharp.Shop;

[Route("/")] [AllowAnonymous]
component Front() {
  render {
    ShopSearchPage(showStockUnder: 3) {
      slot card { c =>
        Link(href: c.Href, label: c.Name) {
          Stack(gap: 2, p: 3, bg: Colors.Surface, rounded: Radius.Md) {
            if (c.Photo != "") { Image(src: c.Photo, alt: c.PhotoAlt, aspectRatio: "1 / 1", objectFit: ObjectFit.Cover); }
            Text(c.Name, fontWeight: FontWeight.Bold);
            Text(c.WasPrice == "" ? c.Price : c.Price + " — was " + c.WasPrice);
            if (c.Stock != "") { Text(c.Stock, fontSize: FontSize.Label, color: Colors.TextMuted); }
          }
        }
      }
    }
  }
}

See also#

Related

Slot(item) — let the caller decide what each row looks like

`Slot(item)` renders the caller's template once for that item. It is how a component owns the list — the layout, the…

Slot (child content)

A `Slot` marks where a component renders the content block its caller wrapped around it. Writing `Card { Text("hi"); }`…

Building a look — from an empty kit to a design a store can wear

A look is a kit that draws two contracts: the general site contract (`Osysharp.Site.Looks` — a hero, a quote, the…