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
79 changes: 13 additions & 66 deletions packages/boxel-ui/addon/src/components/card-header/index.gts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Maximize from '@cardstack/boxel-icons/maximize';
import type { MenuDivider } from '@cardstack/boxel-ui/helpers.ts';
import { DropdownArrowDown } from '@cardstack/boxel-ui/icons';
import { on } from '@ember/modifier';
import Component from '@glimmer/component';
import type { ComponentLike } from '@glint/template';
Expand All @@ -16,12 +15,14 @@ import ContextButton from '../context-button/index.gts';
import BoxelDropdown from '../dropdown/index.gts';
import Menu from '../menu/index.gts';
import RealmIcon, { type RealmDisplayInfo } from '../realm-icon/index.gts';
import SelectionCheckmark from '../selection-checkmark/index.gts';
import SelectionMenu from '../selection-menu/index.gts';
import Tooltip from '../tooltip/index.gts';

export interface CardHeaderUtilityMenu {
// Accessible name for the trigger; SelectionMenu defaults to the count.
label?: string;
menuItems: (MenuItem | MenuDivider)[];
triggerText: string;
selectedCount: number;
}

interface Signature {
Expand Down Expand Up @@ -127,31 +128,11 @@ export default class CardHeader extends Component<Signature> {
<div class='actions' data-test-boxel-card-header-actions>
{{#if @utilityMenu}}
<div class='utility-menu-positioner'>
<BoxelDropdown @autoClose={{true}}>
<:trigger as |ddModifier|>
<button
type='button'
class='utility-menu-trigger'
{{ddModifier}}
>
<SelectionCheckmark class='utility-menu-trigger-icon' />
<span class='utility-menu-trigger-text'>
{{@utilityMenu.triggerText}}
</span>
<DropdownArrowDown
class='utility-menu-dropdown-arrow'
width='13px'
height='13px'
/>
</button>
</:trigger>
<:content as |dd|>
<Menu
@items={{@utilityMenu.menuItems}}
@closeMenu={{dd.close}}
/>
</:content>
</BoxelDropdown>
<SelectionMenu
@selectedCount={{@utilityMenu.selectedCount}}
@items={{@utilityMenu.menuItems}}
@label={{@utilityMenu.label}}
/>
</div>
{{/if}}
{{#if @onExpand}}
Expand Down Expand Up @@ -409,51 +390,17 @@ export default class CardHeader extends Component<Signature> {
background-color: var(--boxel-light);
}

/* The selection pill floats out of the actions flex flow, anchored
/* The selection menu floats out of the actions flex flow, anchored
just left of the action buttons. Keeping it out of flow means its
presence doesn't widen the actions column, which would otherwise
shift the centered card title off-center. */
shift the centered card title off-center. With only `right` set
(no width/left), the box shrinks to its content and grows leftward
from that anchor. */
.utility-menu-positioner {
--utility-menu-trigger-height: 1.625rem;
position: absolute;
right: calc(100% + var(--boxel-sp-5xs));
top: 50%;
transform: translateY(-50%);
width: 1px;
height: var(--utility-menu-trigger-height);
}
.utility-menu-trigger {
position: absolute;
top: 0;
right: 0;
display: inline-flex;
align-items: center;
gap: var(--boxel-sp-5xs);
min-height: var(--utility-menu-trigger-height);
width: max-content;
padding: 0 var(--boxel-sp-xs);
border: none;
border-radius: 0.375rem;
background-color: var(--boxel-highlight);
color: var(--boxel-highlight-foreground);
font: 700 var(--boxel-font-sm);
cursor: pointer;
}
.utility-menu-trigger:hover:not(:disabled),
.utility-menu-trigger:focus-visible:not(:disabled) {
background-color: var(--boxel-highlight-hover);
}
.utility-menu-trigger-icon {
width: 0.875rem;
height: 0.875rem;
flex-shrink: 0;
}
.utility-menu-trigger-text {
line-height: 1;
}
.utility-menu-dropdown-arrow {
margin-left: 0;
vertical-align: middle;
}
@container card-header (min-width: 30rem) {
.card-type-display-name {
Expand Down
4 changes: 2 additions & 2 deletions packages/boxel-ui/addon/src/components/card-header/usage.gts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default class CardHeaderUsage extends Component {
};

@tracked utilityMenu?: CardHeaderUtilityMenu = {
triggerText: '2 Selected',
selectedCount: 2,
menuItems: [
new MenuItem({
label: 'Deselect All',
Expand Down Expand Up @@ -222,7 +222,7 @@ export default class CardHeaderUsage extends Component {
/>
<Args.Object
@name='utilityMenu'
@description='when present, renders a utility dropdown with { triggerText, menuItems }'
@description='when present, renders a SelectionMenu built from { selectedCount, menuItems, label }'
@value={{this.utilityMenu}}
/>
<Args.Action
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module('Integration | Component | card-header', function (hooks) {
};
const noop = () => {};
const utilityMenu = {
triggerText: '2',
selectedCount: 2,
menuItems: [new MenuItem({ label: 'Deselect All', action: noop })],
};

Expand Down Expand Up @@ -77,7 +77,7 @@ module('Integration | Component | card-header', function (hooks) {
let offsetWithMenu = titleCenterWithMenu - headerCenterWithMenu;

assert
.dom('[data-test-card-header] .utility-menu-trigger')
.dom('[data-test-card-header] [data-test-selection-dropdown-trigger]')
.exists('the selection utility menu pill is rendered');

assert.ok(
Expand Down
5 changes: 4 additions & 1 deletion packages/host/app/components/operator-mode/stack-item.gts
Original file line number Diff line number Diff line change
Expand Up @@ -599,8 +599,11 @@ export default class OperatorModeStackItem extends Component<Signature> {
);

return {
triggerText: `${selectedCount}`,
selectedCount,
menuItems,
label: `Selection menu, ${selectedCount} card${
selectedCount === 1 ? '' : 's'
} selected`,
};
}

Expand Down
34 changes: 17 additions & 17 deletions packages/host/tests/acceptance/workspace-delete-multiple-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,17 @@ module('Acceptance | workspace-delete-multiple', function (hooks) {

// Verify selection state is active
assert
.dom('.utility-menu-trigger')
.dom('[data-test-selection-dropdown-trigger]')
.containsText('1', 'Selection chip shows count');

// Select additional cards
await selectCard('Pet/2');

// Verify selection count
assert.dom('.utility-menu-trigger').containsText('2');
assert.dom('[data-test-selection-dropdown-trigger]').containsText('2');

// Open utility menu
await click('.utility-menu-trigger');
await click('[data-test-selection-dropdown-trigger]');

// Click bulk delete option
await click('[data-test-boxel-menu-item-text="Delete 2 items"]');
Expand Down Expand Up @@ -180,7 +180,7 @@ module('Acceptance | workspace-delete-multiple', function (hooks) {

// Verify selection mode is cleared
assert
.dom('.utility-menu-trigger')
.dom('[data-test-selection-dropdown-trigger]')
.doesNotExist('Selection summary is cleared');
});

Expand Down Expand Up @@ -209,17 +209,17 @@ module('Acceptance | workspace-delete-multiple', function (hooks) {
await selectCard('Pet/3');

// Verify selection count
assert.dom('.utility-menu-trigger').containsText('3');
assert.dom('[data-test-selection-dropdown-trigger]').containsText('3');

// Open utility menu
await click('.utility-menu-trigger');
await click('[data-test-selection-dropdown-trigger]');

// Click "Deselect All" option
await click('[data-test-boxel-menu-item-text="Deselect All"]');

// Verify selection is cleared
assert
.dom('.utility-menu-trigger')
.dom('[data-test-selection-dropdown-trigger]')
.doesNotExist('Selection summary is cleared after deselect');

// Verify overlay chrome is gone. The overlay clears on hover-out via a
Expand Down Expand Up @@ -254,22 +254,22 @@ module('Acceptance | workspace-delete-multiple', function (hooks) {

// Verify selection state is active
assert
.dom('.utility-menu-trigger')
.dom('[data-test-selection-dropdown-trigger]')
.containsText('1', 'Selection chip shows count');

// Open utility menu
await click('.utility-menu-trigger');
await click('[data-test-selection-dropdown-trigger]');

// Click "Select All" option
await click('[data-test-boxel-menu-item-text="Select All"]');

// Verify all cards are selected
assert
.dom('.utility-menu-trigger')
.dom('[data-test-selection-dropdown-trigger]')
.containsText(`${totalCardCount}`, 'All cards are now selected');

// Open utility menu again to verify "Select All" is no longer available
await click('.utility-menu-trigger');
await click('[data-test-selection-dropdown-trigger]');

// "Select All" should not be available when all cards are selected
assert
Expand Down Expand Up @@ -318,18 +318,18 @@ module('Acceptance | workspace-delete-multiple', function (hooks) {
};

try {
await click('.utility-menu-trigger');
await click('[data-test-selection-dropdown-trigger]');
let countBeforeSelectAll = getCallCount;
await click('[data-test-boxel-menu-item-text="Select All"]');
await waitUntil(() =>
document
.querySelector('.utility-menu-trigger')
.querySelector('[data-test-selection-dropdown-trigger]')
?.textContent?.includes(`${totalCardCount}`),
);
let getsDuringSelectAll = getCallCount - countBeforeSelectAll;

assert
.dom('.utility-menu-trigger')
.dom('[data-test-selection-dropdown-trigger]')
.containsText(`${totalCardCount}`, 'All cards are now selected');
assert.strictEqual(
getsDuringSelectAll,
Expand Down Expand Up @@ -366,10 +366,10 @@ module('Acceptance | workspace-delete-multiple', function (hooks) {
await selectCard('Pet/2');

// Verify selection count
assert.dom('.utility-menu-trigger').containsText('2');
assert.dom('[data-test-selection-dropdown-trigger]').containsText('2');

// Open utility menu
await click('.utility-menu-trigger');
await click('[data-test-selection-dropdown-trigger]');

// Click bulk delete option
await click('[data-test-boxel-menu-item-text="Delete 2 items"]');
Expand Down Expand Up @@ -400,7 +400,7 @@ module('Acceptance | workspace-delete-multiple', function (hooks) {

// Verify selection is still active
assert
.dom('.utility-menu-trigger')
.dom('[data-test-selection-dropdown-trigger]')
.containsText('2', 'Selection remains active after cancel');
});
});
Loading