From 121457661705c6defacb06ec254efa0a4cc1f82a Mon Sep 17 00:00:00 2001 From: Burcu Noyan Date: Thu, 19 Feb 2026 13:06:00 -0500 Subject: [PATCH 1/9] add FittedCardContainer component and helpers --- packages/boxel-ui/addon/src/components.ts | 2 + .../src/components/basic-fitted/usage.gts | 132 +------------- .../fitted-card-container/index.gts | 57 +++++++ .../fitted-card-container/usage.gts | 88 ++++++++++ packages/boxel-ui/addon/src/helpers.ts | 2 + packages/boxel-ui/addon/src/usage.ts | 2 + .../addon/src/utils/fitted-formats.ts | 161 ++++++++++++++++++ 7 files changed, 320 insertions(+), 124 deletions(-) create mode 100644 packages/boxel-ui/addon/src/components/fitted-card-container/index.gts create mode 100644 packages/boxel-ui/addon/src/components/fitted-card-container/usage.gts create mode 100644 packages/boxel-ui/addon/src/utils/fitted-formats.ts diff --git a/packages/boxel-ui/addon/src/components.ts b/packages/boxel-ui/addon/src/components.ts index f3e343eec82..d23ec18c805 100644 --- a/packages/boxel-ui/addon/src/components.ts +++ b/packages/boxel-ui/addon/src/components.ts @@ -26,6 +26,7 @@ import EntityDisplayWithIcon from './components/entity-icon-display/index.gts'; import EntityDisplayWithThumbnail from './components/entity-thumbnail-display/index.gts'; import FieldContainer from './components/field-container/index.gts'; import FilterList, { type Filter } from './components/filter-list/index.gts'; +import FittedCardContainer from './components/fitted-card-container/index.gts'; import GridContainer from './components/grid-container/index.gts'; import BoxelHeader from './components/header/index.gts'; import Header from './components/header/index.gts'; @@ -115,6 +116,7 @@ export { EntityDisplayWithThumbnail, FieldContainer, FilterList, + FittedCardContainer, GridContainer, Header, IconButton, diff --git a/packages/boxel-ui/addon/src/components/basic-fitted/usage.gts b/packages/boxel-ui/addon/src/components/basic-fitted/usage.gts index 1933062ee52..8bea15cc159 100644 --- a/packages/boxel-ui/addon/src/components/basic-fitted/usage.gts +++ b/packages/boxel-ui/addon/src/components/basic-fitted/usage.gts @@ -5,134 +5,18 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import FreestyleUsage from 'ember-freestyle/components/freestyle/usage'; -import { cn, gt, gte } from '../../helpers.ts'; +import { + cn, + gt, + gte, + FITTED_FORMATS, + type FittedFormatSpec, +} from '../../helpers.ts'; import type { Icon } from '../../icons.ts'; import CardContainer from '../card-container/index.gts'; import BasicFitted from './index.gts'; -type Spec = { height: number; title?: string; width: number }; - -// These can be imported from the @cardstack/runtime-common package: -// `import { FITTED_FORMATS } from '@cardstack/runtime-common'` -// For various build problems, could not do that here. -const FITTED_FORMATS = [ - { - name: 'Badges', - specs: [ - { - id: 'small-badge', - title: 'Small Badge', - width: 150, - height: 40, - }, - { - id: 'medium-badge', - title: 'Medium Badge', - width: 150, - height: 65, - }, - { - id: 'large-badge', - title: 'Large Badge', - width: 150, - height: 105, - }, - ], - }, - { - name: 'Strips', - specs: [ - { - id: 'single-strip', - title: 'Single Strip', - width: 250, - height: 40, - }, - { - id: 'double-strip', - title: 'Double Strip', - width: 250, - height: 65, - }, - { - id: 'triple-strip', - title: 'Triple Strip', - width: 250, - height: 105, - }, - { - id: 'double-wide-strip', - title: 'Double Wide Strip', - width: 400, - height: 65, - }, - { - id: 'triple-wide-strip', - title: 'Triple Wide Strip', - width: 400, - height: 105, - }, - ], - }, - { - name: 'Tiles', - specs: [ - { - id: 'small-tile', - title: 'Small Tile', - width: 150, - height: 170, - }, - { - id: 'regular-tile', - title: 'Regular Tile', - width: 250, - height: 170, - }, - { - id: 'cardsgrid-tile', - title: 'CardsGrid Tile', - width: 170, - height: 250, - }, - { - id: 'tall-tile', - title: 'Tall Tile', - width: 150, - height: 275, - }, - { - id: 'large-tile', - title: 'Large Tile', - width: 250, - height: 275, - }, - ], - }, - { - name: 'Cards', - specs: [ - { - id: 'compact-card', - title: 'Compact Card', - width: 400, - height: 170, - }, - { - id: 'full-card', - title: 'Full Card', - width: 400, - height: 275, - }, - { - id: 'expanded-card', - title: 'Expanded Card', - width: 400, - height: 445, - }, - ], - }, -]; +type Spec = Partial & { width: number; height: number }; const OTHER_SIZES: Spec[] = [ { width: 226, height: 226 }, diff --git a/packages/boxel-ui/addon/src/components/fitted-card-container/index.gts b/packages/boxel-ui/addon/src/components/fitted-card-container/index.gts new file mode 100644 index 00000000000..b9c10679456 --- /dev/null +++ b/packages/boxel-ui/addon/src/components/fitted-card-container/index.gts @@ -0,0 +1,57 @@ +import Component from '@glimmer/component'; + +import { + fittedFormatById, + fittedFormatIds, + sanitizeHtmlSafe, + type FittedFormatId, +} from '../../helpers.ts'; + +interface Signature { + Args: { + size?: FittedFormatId; + }; + Blocks: { default: [] }; + Element: HTMLDivElement; +} + +export default class FittedCardContainer extends Component { + + + get formatSpec() { + let size = this.args.size; + + if (!size) { + return null; + } + + if (!fittedFormatIds?.includes(size)) { + console.error( + `Size "${size}" does not exist in fitted format sizes. Please choose from ${fittedFormatIds.join(', ')}`, + ); + return null; + } + + return fittedFormatById.get(size) ?? null; + } + + get containerStyle() { + let formatSpec = this.formatSpec; + + if (!formatSpec) { + return sanitizeHtmlSafe(''); + } + + return sanitizeHtmlSafe( + `width: ${formatSpec.width}px; height: ${formatSpec.height}px;`, + ); + } +} diff --git a/packages/boxel-ui/addon/src/components/fitted-card-container/usage.gts b/packages/boxel-ui/addon/src/components/fitted-card-container/usage.gts new file mode 100644 index 00000000000..d74ca6ebb26 --- /dev/null +++ b/packages/boxel-ui/addon/src/components/fitted-card-container/usage.gts @@ -0,0 +1,88 @@ +import Component from '@glimmer/component'; +import FreestyleUsage from 'ember-freestyle/components/freestyle/usage'; + +import { + fittedFormatById, + fittedFormatIds, + type FittedFormatId, +} from '../../helpers.ts'; +import FittedCardContainer from './index.gts'; + +export default class FittedCardContainerUsage extends Component { + sampleSizes = fittedFormatIds; + + formatTitle(size: FittedFormatId) { + return fittedFormatById.get(size)?.title ?? size; + } + + formatDimensions(size: FittedFormatId) { + let spec = fittedFormatById.get(size); + return spec ? `${spec.width}px × ${spec.height}px` : ''; + } + + +} diff --git a/packages/boxel-ui/addon/src/helpers.ts b/packages/boxel-ui/addon/src/helpers.ts index b63211d3813..ac355f81ef4 100644 --- a/packages/boxel-ui/addon/src/helpers.ts +++ b/packages/boxel-ui/addon/src/helpers.ts @@ -63,6 +63,8 @@ import type { NormalizePhoneFormatResult } from './helpers/validate-phone-format export * from './helpers/color-tools.ts'; +export * from './utils/fitted-formats.ts'; + export { add, addClassToSVG, diff --git a/packages/boxel-ui/addon/src/usage.ts b/packages/boxel-ui/addon/src/usage.ts index 0d2f7453846..460952182a8 100644 --- a/packages/boxel-ui/addon/src/usage.ts +++ b/packages/boxel-ui/addon/src/usage.ts @@ -24,6 +24,7 @@ import EntityIconDisplayUsage from './components/entity-icon-display/usage.gts'; import EntityThumbnailDisplayUsage from './components/entity-thumbnail-display/usage.gts'; import FieldContainerUsage from './components/field-container/usage.gts'; import FilterListUsage from './components/filter-list/usage.gts'; +import FittedCardContainerUsage from './components/fitted-card-container/usage.gts'; import GridContainerUsage from './components/grid-container/usage.gts'; import HeaderUsage from './components/header/usage.gts'; import IconButtonUsage from './components/icon-button/usage.gts'; @@ -77,6 +78,7 @@ export const ALL_USAGE_COMPONENTS = [ ['EntityThumbnailDisplay', EntityThumbnailDisplayUsage], ['FieldContainer', FieldContainerUsage], ['FilterList', FilterListUsage], + ['FittedCardContainer', FittedCardContainerUsage], ['GridContainer', GridContainerUsage], ['Header', HeaderUsage], ['IconButton', IconButtonUsage], diff --git a/packages/boxel-ui/addon/src/utils/fitted-formats.ts b/packages/boxel-ui/addon/src/utils/fitted-formats.ts new file mode 100644 index 00000000000..5112e7fd010 --- /dev/null +++ b/packages/boxel-ui/addon/src/utils/fitted-formats.ts @@ -0,0 +1,161 @@ +export type FittedFormatId = + | 'cardsgrid-tile' + | 'compact-card' + | 'double-strip' + | 'double-wide-strip' + | 'expanded-card' + | 'full-card' + | 'large-badge' + | 'large-tile' + | 'medium-badge' + | 'regular-tile' + | 'single-strip' + | 'small-badge' + | 'small-tile' + | 'tall-tile' + | 'triple-strip' + | 'triple-wide-strip'; + +export type FittedFormatSpec = { + id: FittedFormatId; + title: string; + width: number; + height: number; +}; + +type FittedFormatGallery = ReadonlyArray<{ + name: string; + specs: FittedFormatSpec[]; +}>; + +export const FITTED_FORMATS: FittedFormatGallery = [ + { + name: 'Badges', + specs: [ + { + id: 'small-badge', + title: 'Small Badge', + width: 150, + height: 40, + }, + { + id: 'medium-badge', + title: 'Medium Badge', + width: 150, + height: 65, + }, + { + id: 'large-badge', + title: 'Large Badge', + width: 150, + height: 105, + }, + ], + }, + { + name: 'Strips', + specs: [ + { + id: 'single-strip', + title: 'Single Strip', + width: 250, + height: 40, + }, + { + id: 'double-strip', + title: 'Double Strip', + width: 250, + height: 65, + }, + { + id: 'triple-strip', + title: 'Triple Strip', + width: 250, + height: 105, + }, + { + id: 'double-wide-strip', + title: 'Double Wide Strip', + width: 400, + height: 65, + }, + { + id: 'triple-wide-strip', + title: 'Triple Wide Strip', + width: 400, + height: 105, + }, + ], + }, + { + name: 'Tiles', + specs: [ + { + id: 'small-tile', + title: 'Small Tile', + width: 150, + height: 170, + }, + { + id: 'regular-tile', + title: 'Regular Tile', + width: 250, + height: 170, + }, + { + id: 'cardsgrid-tile', + title: 'CardsGrid Tile', + width: 170, + height: 250, + }, + { + id: 'tall-tile', + title: 'Tall Tile', + width: 150, + height: 275, + }, + { + id: 'large-tile', + title: 'Large Tile', + width: 250, + height: 275, + }, + ], + }, + { + name: 'Cards', + specs: [ + { + id: 'compact-card', + title: 'Compact Card', + width: 400, + height: 170, + }, + { + id: 'full-card', + title: 'Full Card', + width: 400, + height: 275, + }, + { + id: 'expanded-card', + title: 'Expanded Card', + width: 400, + height: 445, + }, + ], + }, +]; + +export const FITTED_FORMAT_SIZES = FITTED_FORMATS.flatMap( + (group) => group.specs, +); + +export const fittedFormatIds = FITTED_FORMAT_SIZES.flatMap( + (formatSpec) => formatSpec.id, +); + +export const fittedFormatById: ReadonlyMap = + new Map( + FITTED_FORMAT_SIZES.map((format) => [format.id, format]), + ); From 61bdbb7d738525f97e379536ea3a64b37504fdab Mon Sep 17 00:00:00 2001 From: Burcu Noyan Date: Thu, 19 Feb 2026 19:20:07 -0500 Subject: [PATCH 2/9] add grid container options for fitted card --- .../src/components/grid-container/index.gts | 84 ++++++++++++++----- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/packages/boxel-ui/addon/src/components/grid-container/index.gts b/packages/boxel-ui/addon/src/components/grid-container/index.gts index 6068bb307b5..52fc6b11107 100644 --- a/packages/boxel-ui/addon/src/components/grid-container/index.gts +++ b/packages/boxel-ui/addon/src/components/grid-container/index.gts @@ -1,9 +1,16 @@ -import type { TemplateOnlyComponent } from '@ember/component/template-only'; +import Component from '@glimmer/component'; -import { element } from '../../helpers.ts'; +import { + element, + fittedFormatById, + fittedFormatIds, + sanitizeHtmlSafe, + type FittedFormatId, +} from '../../helpers.ts'; interface Signature { Args: { + size?: FittedFormatId; tag?: keyof HTMLElementTagNameMap; }; Blocks: { @@ -12,28 +19,61 @@ interface Signature { Element: HTMLElement; } -const GridContainer: TemplateOnlyComponent = + + get formatSpec() { + let size = this.args.size; + + if (!size) { + return null; + } + + if (!fittedFormatIds?.includes(size)) { + console.error( + `Size "${size}" does not exist in fitted format sizes. Please choose from ${fittedFormatIds.join(', ')}`, + ); + return null; } - -; -export default GridContainer; + return fittedFormatById.get(size) ?? null; + } + + get containerStyle() { + let formatSpec = this.formatSpec; + + if (!formatSpec) { + return sanitizeHtmlSafe(''); + } + + return sanitizeHtmlSafe( + `grid-template-columns: repeat(auto-fill, ${formatSpec.width}px); grid-auto-rows: ${formatSpec.height}px;`, + ); + } +} From a2927805e261acbade433d2718b4fd44f879bed6 Mon Sep 17 00:00:00 2001 From: Burcu Noyan Date: Thu, 19 Feb 2026 19:20:38 -0500 Subject: [PATCH 3/9] adjust card picker --- .../components/card-search/item-button.gts | 24 ++----- .../components/card-search/search-content.gts | 66 ++++++++++++------- .../card-search/search-result-section.gts | 57 +++++++++------- 3 files changed, 83 insertions(+), 64 deletions(-) diff --git a/packages/host/app/components/card-search/item-button.gts b/packages/host/app/components/card-search/item-button.gts index f8881e46ba0..da477b82b90 100644 --- a/packages/host/app/components/card-search/item-button.gts +++ b/packages/host/app/components/card-search/item-button.gts @@ -140,7 +140,7 @@ export default class ItemButton extends Component { {{this.cardRefName}} {{else}} -
+
{{#if this.componentItem}} {{/if}} {{#if this.urlForRealmLookup}} {{#let (this.realm.info this.urlForRealmLookup) as |realmInfo|}} -
{{realmInfo.name}}
+
in + {{realmInfo.name}}
{{/let}} {{/if}}
@@ -198,14 +194,9 @@ export default class ItemButton extends Component { --boxel-button-padding: 0; --boxel-button-border-radius: var(--boxel-border-radius); --boxel-button-border: 1px solid var(--boxel-200); - height: var(--item-height, 67px); + height: 100%; width: 100%; max-width: 100%; - overflow: hidden; - container-name: fitted-card; - container-type: size; - display: flex; - text-align: left; } .catalog-item.selected { border-color: var(--boxel-highlight); @@ -217,10 +208,6 @@ export default class ItemButton extends Component { .catalog-item.selected:hover { border-color: var(--boxel-highlight); } - .catalog-item.compact { - width: var(--item-width, 250px); - height: var(--item-height, 40px); - } .create-card.catalog-item { --boxel-button-padding: var(--boxel-sp-xs) var(--boxel-sp); --boxel-button-letter-spacing: var(--boxel-lsp-xs); @@ -239,6 +226,9 @@ export default class ItemButton extends Component { flex-direction: column; align-items: self-end; width: 100%; + height: 100%; + max-width: 100%; + max-height: 100%; } .realm-name { font: 400 var(--boxel-font); diff --git a/packages/host/app/components/card-search/search-content.gts b/packages/host/app/components/card-search/search-content.gts index f91d4f1e66e..a416e75cefa 100644 --- a/packages/host/app/components/card-search/search-content.gts +++ b/packages/host/app/components/card-search/search-content.gts @@ -529,6 +529,11 @@ export default class SearchContent extends Component { private get sections(): SearchSheetSection[] { const sections: SearchSheetSection[] = []; + // Add recents section if enabled + if (this.showRecents && this.recentCardsSection) { + sections.push(this.recentCardsSection); + } + // Add URL section if present if (this.cardByUrlSection) { sections.push(this.cardByUrlSection); @@ -539,11 +544,6 @@ export default class SearchContent extends Component { sections.push(...this.cardsByQuerySection); } - // Add recents section if enabled - if (this.showRecents && this.recentCardsSection) { - sections.push(this.recentCardsSection); - } - return sections; } @@ -621,24 +621,44 @@ export default class SearchContent extends Component { {{/if}} {{/if}} - {{! Render all sections }} - {{#each this.sections as |section i|}} - - {{/each}} + {{#if @isCompact}} + {{#if this.recentCardsSection}} + + {{/if}} + {{else}} + {{! Render all sections }} + {{#each this.sections as |section i|}} + + {{/each}} + {{/if}} {{#if this.hasNoResults}}
diff --git a/packages/host/app/components/card-search/search-result-section.gts b/packages/host/app/components/card-search/search-result-section.gts index b72a5394933..309a34d2283 100644 --- a/packages/host/app/components/card-search/search-result-section.gts +++ b/packages/host/app/components/card-search/search-result-section.gts @@ -7,7 +7,11 @@ import Component from '@glimmer/component'; import HistoryIcon from '@cardstack/boxel-icons/history'; import pluralize from 'pluralize'; -import { Button } from '@cardstack/boxel-ui/components'; +import { + Button, + GridContainer, + FittedCardContainer, +} from '@cardstack/boxel-ui/components'; import { eq } from '@cardstack/boxel-ui/helpers'; import type { CodeRef } from '@cardstack/runtime-common'; @@ -211,7 +215,10 @@ export default class SearchResultSection extends Component { @onShowOnlyChange={{this.handleShowOnlyChange}} /> {{/unless}} -
+ {{#if (this.showCreateForRealm this.realmSection.realmUrl)}} { /> {{/unless}} {{/each}} -
+ {{#if this.displayShowMore}}