import { css, html, nothing } from 'lit'; import { dreamSearchEndpoints } from '../generated/endpoint-defaults.js'; import type { SearchDreamSymbolsResponse } from '../types/index.js'; import { RoxyDataElement } from '../utils/base-element.js'; import { baseStyles } from '../utils/base-styles.js'; import { customElement } from '../utils/define.js'; /** * Dream-symbol search results. Renders /dreams/symbols: the dictionary, a letter of it or the matches for a `q` search, as selectable tiles. In self-fetch mode the list opens on the whole dictionary under a search box and an A to Z bar, pages on with Show more, and a picked tile opens its roxy-dream-card under the list with the full meaning, so a hosted embed completes the flow on its own. Every pick also emits a `roxy-symbol-select` CustomEvent ({ id, name, letter }) that bubbles and is composed, which is how a controlled host, the one holding the key, pairs the list with a card of its own. This is the dreams form-mode analog of roxy-location-search. */ @customElement('roxy-dream-search') export class RoxyDreamSearch extends RoxyDataElement { static endpoints = dreamSearchEndpoints; static styles = [ baseStyles, css` .wrap { background: var(--roxy-surface, #fff); color: var(--roxy-fg, #0a0a0a); border: 1px solid var(--roxy-border, #e4e4e7); border-radius: var(--roxy-radius-md, 8px); padding: var(--roxy-space-lg, 1.5rem); box-shadow: var(--roxy-shadow-sm); display: grid; /* Never an implicit auto column: it floors at min-content, so one long * unbreakable string widens the track past the padded card. */ grid-template-columns: minmax(0, 1fr); gap: var(--roxy-space-md, 1rem); } .head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--roxy-space-md, 1rem); flex-wrap: wrap; } .title { font-size: var(--roxy-text-lg, 1.125rem); font-weight: var(--roxy-weight-bold, 600); margin: 0; } .count { color: var(--roxy-muted, #71717a); font-size: var(--roxy-text-sm, 0.875rem); } .grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); gap: var(--roxy-space-sm, 0.5rem); } .result { display: flex; align-items: center; gap: var(--roxy-space-sm, 0.5rem); width: 100%; text-align: left; font: inherit; color: var(--roxy-fg, #0a0a0a); background: transparent; border: 1px solid var(--roxy-border, #e4e4e7); border-radius: var(--roxy-radius-md, 8px); padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem); cursor: pointer; transition: border-color 0.12s ease, background 0.12s ease; } .result[aria-pressed='true'], .result:hover, .result:focus-visible { border-color: var(--roxy-accent, #f59e0b); background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 8%, transparent); outline: none; } .detail { margin-top: var(--roxy-space-md, 1rem); } .letter { flex: none; width: 1.6rem; height: 1.6rem; display: grid; place-items: center; border-radius: var(--roxy-radius-full, 9999px); background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 16%, transparent); color: var(--roxy-fg, #0a0a0a); font-size: var(--roxy-text-xs, 0.75rem); font-weight: var(--roxy-weight-bold, 600); text-transform: uppercase; } .name { font-size: var(--roxy-text-sm, 0.875rem); font-weight: 500; } `, ]; protected rowDetail = { tag: 'roxy-dream-card', path: 'dreams/symbols/{id}' }; protected listKey = 'symbols'; protected renderData(d: SearchDreamSymbolsResponse) { const symbols = d.symbols ?? []; if (symbols.length === 0) return this.renderEmpty(); const total = typeof d.total === 'number' ? d.total : symbols.length; return html`

${this.t('Dream symbols')}

${total === 1 ? this.t('1 symbol') : this.t('{{count}} symbols', { count: total })}
${this.renderMore()} ${this.openedRow ? html`
${this.openedRow}
` : nothing}
`; } } declare global { interface HTMLElementTagNameMap { 'roxy-dream-search': RoxyDreamSearch; } }