# `orderly-product-rail`

`orderly-product-rail` renders a `SearchQuery` as a horizontal product list with a heading and optional CTA link. It composes `orderly-product-grid`, so search execution, product rendering, loading state, basket wiring, and default query merging follow the same behavior as the grid.

Use it for homepage rows, category highlights, editorial collections, and "more like this" rails.

## Basic Markup

```html
<orderly-product-rail
  title="Sko"
  cta-label="Se alle"
  cta-href="/categories/?category=sko"
  keywords="sneakers | støvler | sandaler"
  tags="sko"
  order-by="CreatedTime desc">
</orderly-product-rail>
```

Read [Search Syntax](../search-syntax.md) before writing homepage/category rails. `keywords` maps to `SearchQuery.Query`; whitespace narrows the backend query and `|` is the OR operator. Multiple `tags` are narrowing AND-style filters, not alternatives.

## Child Query Markup

```html
<orderly-product-rail
  title="Sko"
  cta-label="Se alle"
  cta-href="/categories/?category=sko">
  <query>
    <tags>sko</tags>
    <keywords>sneakers | støvler | sandaler</keywords>
    <order-by>CreatedTime desc</order-by>
  </query>
</orderly-product-rail>
```

The child `<query>` element is hidden by the component and is only used as configuration.

## Typed Query

Use the typed `query` property for the full Core contract:

```ts
import { create } from "@bufbuild/protobuf";
import { SearchQuerySchema, TagSchema } from "@orderlyshop/core-client";

productRail.query = create(SearchQuerySchema, {
  Query: "sneakers | støvler | sandaler",
  Tags: [
    create(TagSchema, { Value: "sko" })
  ],
  OrderBy: ["CreatedTime desc"]
});
```

## Default Query Scope

`defaultQuery` is merged with the rail query before the backend search call. This is useful for tenant scope, published-only filters, store scope, or other shop-wide constraints.

```ts
productRail.defaultQuery = defaultQuery;
```

Global default query:

```ts
configureShop({ defaultShop: { defaultQuery } });
```

When both are present, the per-rail `defaultQuery` is merged with the global default query and then the rail's own query is applied.

## Attributes

- `base-url`: Backend URL used when no `client` property is assigned.
- `title`: Heading text.
- `cta-label`: CTA link label. Hidden when empty.
- `cta-href`: CTA link URL.
- `empty-label`: Empty result copy.
- `loading-label`: Loading copy forwarded to the internal grid.
- `error-label`: Error copy forwarded to the internal grid.
- `keywords`: Maps to `SearchQuery.Query`.
- `tags`: Comma-separated values mapped to `SearchQuery.Tags`.
- `hidden-query`: Maps to `SearchQuery.HiddenQuery`.
- `order-by`: Comma-separated values mapped to `SearchQuery.OrderBy`.
- `store-id`: Maps to `SearchQuery.StoreId`.
- `featured`: Boolean mapped to `SearchQuery.Featured`.

## Properties

- `client: OrderlyClient | undefined`: Explicit client instance.
- `query: SearchQuery | undefined`: Explicit search query. Takes precedence over declarative query markup.
- `defaultQuery: SearchQuery | undefined`: Per-rail default query merged before `query`.
- `shopQuery: SearchQuery | undefined`: Backward-compatible alias for `defaultQuery`.
- `basketController: BasketController | undefined`: Shared basket state.

## Templates

### `layout`

Replaces the outer rail structure. Include a `data-orderly-slot="grid"` target.

```html
<template data-orderly-template="layout">
  <section class="my-rail">
    <header>
      <h2 data-orderly-bind="title"></h2>
      <a data-orderly-bind="ctaHref">
        <span data-orderly-bind="ctaLabel"></span>
      </a>
    </header>
    <div data-orderly-slot="grid"></div>
  </section>
</template>
```

### `grid`

Replaces the internal grid layout. Include:

- `data-orderly-bind="status"` for loading/error text.
- `data-orderly-slot="items"` for product items.

```html
<template data-orderly-template="grid">
  <section class="my-horizontal-list">
    <p data-orderly-bind="status"></p>
    <div data-orderly-slot="items"></div>
  </section>
</template>
```

### Product State Templates

These are forwarded to the internal `orderly-product-grid`:

- `product`
- `loading`
- `empty`

## Events

Product events bubble from child product tiles:

- `orderly-product-selected`
- `orderly-add-to-basket`
- `orderly-remove-from-basket`
- `orderly-results`
- `orderly-error`

## Styling

The default rail uses light DOM and stable class names:

- `orderly-product-rail`
- `orderly-product-rail__header`
- `orderly-product-rail__title`
- `orderly-product-rail__cta`
- `orderly-product-rail__items`
- `orderly-product-rail__status`
- `orderly-product-rail__empty`

CSS custom properties:

- `--orderly-product-rail-item-width`
- `--orderly-product-rail-gap`
- `--orderly-product-rail-padding`

Example:

```css
orderly-product-rail {
  --orderly-product-rail-item-width: minmax(220px, 260px);
  --orderly-product-rail-gap: 18px;
}
```
