Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions packages/boxel-ui/addon/src/components/picker/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { tracked } from '@glimmer/tracking';
import type { ComponentLike } from '@glint/template';
import { modifier } from 'ember-modifier';
import type { Select } from 'ember-power-select/components/power-select';
import { includes } from 'lodash';

import type { Icon } from '../../icons/types.ts';
import LoadingIndicator from '../loading-indicator/index.gts';
Expand Down Expand Up @@ -192,7 +191,9 @@ export default class Picker extends Component<PickerSignature> {
}

get isSelected() {
return (option: PickerOption) => includes(this.args.selected, option);
return (option: PickerOption) => {
return this.args.selected.some((o) => o.id === option.id);
};
}

isLastOption = (option: PickerOption): boolean => {
Expand Down Expand Up @@ -251,10 +252,12 @@ export default class Picker extends Component<PickerSignature> {
}

onToggleItem = (item: PickerOption) => {
const isCurrentlySelected = this.args.selected.includes(item);
const isCurrentlySelected = this.args.selected.some(
(o) => o.id === item.id,
);
let newSelected: PickerOption[];
if (isCurrentlySelected) {
newSelected = this.args.selected.filter((o) => o !== item);
newSelected = this.args.selected.filter((o) => o.id !== item.id);
} else {
newSelected = [...this.args.selected, item];
}
Expand All @@ -263,7 +266,9 @@ export default class Picker extends Component<PickerSignature> {

onChange = (selected: PickerOption[]) => {
// Ignore clicks on disabled options
const lastAdded = selected.find((opt) => !this.args.selected.includes(opt));
const lastAdded = selected.find(
(opt) => !this.args.selected.some((o) => o.id === opt.id),
);
if (lastAdded?.disabled) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,29 @@ module('Integration | Component | picker', function (hooks) {
// Check that selected items are displayed (they should be in pills)
await click('[data-test-boxel-picker-trigger]');
assert.dom('[data-test-boxel-picker-selected-item]').exists({ count: 2 });

// Selected options should have checked checkboxes in the dropdown
assert
.dom(
'[data-test-boxel-picker-option-row="1"] .picker-option-row__checkbox--selected',
)
.exists('Option 1 checkbox is checked');
assert
.dom(
'[data-test-boxel-picker-option-row="2"] .picker-option-row__checkbox--selected',
)
.exists('Option 2 checkbox is checked');
// Unselected options should have unchecked checkboxes
assert
.dom(
'[data-test-boxel-picker-option-row="3"] .picker-option-row__checkbox--selected',
)
.doesNotExist('Option 3 checkbox is unchecked');
assert
.dom(
'[data-test-boxel-picker-option-row="4"] .picker-option-row__checkbox--selected',
)
.doesNotExist('Option 4 checkbox is unchecked');
});

test('picker opens dropdown when clicked', async function (assert) {
Expand Down Expand Up @@ -207,7 +230,14 @@ module('Integration | Component | picker', function (hooks) {
'1',
'Should have selected first option',
);
// Checkbox should be checked for selected option
assert
.dom(
'[data-test-boxel-picker-option-row="1"] .picker-option-row__checkbox--selected',
)
.exists('Option 1 checkbox is checked after selecting');

// Click again to deselect — should fall back to select-all
await click(
firstOption.closest('.ember-power-select-option') as HTMLElement,
);
Expand All @@ -216,6 +246,12 @@ module('Integration | Component | picker', function (hooks) {
1,
'Select-all option cannot be deselected',
);
// Checkbox should be unchecked after deselecting
assert
.dom(
'[data-test-boxel-picker-option-row="1"] .picker-option-row__checkbox--selected',
)
.doesNotExist('Option 1 checkbox is unchecked after deselecting');
});

test('picker shows search input when searchEnabled is true', async function (assert) {
Expand Down Expand Up @@ -321,6 +357,17 @@ module('Integration | Component | picker', function (hooks) {
['1'],
'select-all is removed once another option is selected',
);
// Option 1 checkbox should be checked, select-all unchecked
assert
.dom(
'[data-test-boxel-picker-option-row="1"] .picker-option-row__checkbox--selected',
)
.exists('Option 1 checkbox is checked');
assert
.dom(
'[data-test-boxel-picker-option-row="select-all"] .picker-option-row__checkbox--selected',
)
.doesNotExist('Select-all checkbox is unchecked');
});

test('picker selects select-all when it is chosen after other options', async function (assert) {
Expand Down Expand Up @@ -356,6 +403,17 @@ module('Integration | Component | picker', function (hooks) {
['select-all'],
'select-all replaces existing selections when selected',
);
// Select-all checkbox should be checked, Option 1 unchecked
assert
.dom(
'[data-test-boxel-picker-option-row="select-all"] .picker-option-row__checkbox--selected',
)
.exists('Select-all checkbox is checked');
assert
.dom(
'[data-test-boxel-picker-option-row="1"] .picker-option-row__checkbox--selected',
)
.doesNotExist('Option 1 checkbox is unchecked after select-all');
});

test('picker hides remove button for select-all pill', async function (assert) {
Expand Down
15 changes: 10 additions & 5 deletions packages/host/app/components/card-catalog/modal.gts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {

import type { Query } from '@cardstack/runtime-common/query';

import type { NewCardArgs } from '@cardstack/host/utils/card-search/types';

import type { CardDef } from 'https://cardstack.com/base/card-api';

import {
Expand All @@ -46,7 +48,6 @@ import type OperatorModeStateService from '../../services/operator-mode-state-se
import type RealmService from '../../services/realm';
import type RealmServerService from '../../services/realm-server';
import type StoreService from '../../services/store';
import type { NewCardArgs } from '../card-search/utils';

interface Signature {
Args: {};
Expand Down Expand Up @@ -111,9 +112,7 @@ export default class CardCatalogModal extends Component<Signature> {
<SearchPanel
@searchKey={{state.searchKey}}
@baseFilter={{state.baseFilter}}
@availableRealmUrls={{state.availableRealmUrls}}
@consumingRealm={{state.consumingRealm}}
@preselectConsumingRealm={{state.preselectConsumingRealm}}
@initialSelectedRealms={{this.initialSelectedRealmsForPanel}}
as |Bar Content|
>
<ModalContainer
Expand All @@ -134,7 +133,6 @@ export default class CardCatalogModal extends Component<Signature> {
<:header>
<Bar
class='card-catalog-search'
@value={{state.searchKey}}
@onInput={{this.setSearchKey}}
@placeholder='Search for a card or enter card URL'
@pickerDestination='card-catalog-picker-wormhole'
Expand Down Expand Up @@ -242,6 +240,13 @@ export default class CardCatalogModal extends Component<Signature> {
return this.stateStack[this.stateStack.length - 1];
}

private get initialSelectedRealmsForPanel(): URL[] | undefined {
if (!this.state?.preselectConsumingRealm || !this.state?.consumingRealm) {
return undefined;
}
return [this.state.consumingRealm];
}

private get offerToCreateArg() {
if (!this.state) {
return undefined;
Expand Down
7 changes: 7 additions & 0 deletions packages/host/app/components/card-search/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ export const SECTION_SHOW_MORE_INCREMENT = 5;
/**
* Host-local SORT_OPTIONS compatible with realm server query expectations.
* Aligned with SORT_OPTIONS in packages/base/components/cards-grid-layout.gts.
*
* Sort is dual-mode:
* - Server-side: the `sort` field is embedded in the search Query sent to
* the realm server, affecting prerendered search results (realm sections).
* - Client-side: `sortAndFilterRecentCards()` in utils/card-search/recent-cards.ts
* applies the same sort locally to recent cards, matching by `displayName`.
* - URL card lookup: no sort (single card).
*/
export const SORT_OPTIONS: SortOption[] = [
{
Expand Down
8 changes: 5 additions & 3 deletions packages/host/app/components/card-search/item-button.gts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import { isCardInstance } from '@cardstack/runtime-common';

import type RealmService from '@cardstack/host/services/realm';

import {
removeFileExtension,
type NewCardArgs,
} from '@cardstack/host/utils/card-search/types';

import type { CardDef } from 'https://cardstack.com/base/card-api';

import CardRenderer from '../card-renderer';

import { removeFileExtension } from './utils';

import type { NewCardArgs } from './utils';
import type { ComponentLike } from '@glint/template';

type ItemType = ComponentLike<{ Element: Element }> | CardDef | NewCardArgs;
Expand Down
Loading
Loading