Skip to content

Commit 58a08a1

Browse files
authored
Merge pull request #5112 from cardstack/cs-11364-context-button
Use shared ContextButton for the Adorn overlay menu and select buttons
2 parents a26cf4b + 85a912e commit 58a08a1

2 files changed

Lines changed: 106 additions & 49 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { TemplateOnlyComponent } from '@ember/component/template-only';
2+
3+
// The Adorn selection-chip artwork as standalone Icon components, so the
4+
// selection control can be a shared boxel-ui ContextButton: the consumer
5+
// passes the empty variant when unselected and the checked variant when
6+
// selected via `@icon`, and ContextButton supplies the button chrome
7+
// (standard sizing, toggle / aria-pressed, hover background).
8+
//
9+
// Both are two-color composites (teal chip background, highlight-foreground
10+
// circle, teal check), so — like boxel-ui's SelectionCheckmark — they read
11+
// their colors from CSS variables rather than `currentColor`. The chip
12+
// background honors `--adorn-chip-bg` (falling back to the inherited
13+
// `--adorn-accent-light`) so it can be themed independently of the label,
14+
// matching the original AdornSelectChip. They are authored as
15+
// `TemplateOnlyComponent`s whose root is an `<svg>`, so they satisfy the
16+
// boxel-ui `Icon` type (`ComponentLike<{ Element: SVGSVGElement }>`) where
17+
// passed to `@icon`.
18+
interface Signature {
19+
Element: SVGSVGElement;
20+
}
21+
22+
// Empty (unselected): teal rounded-square chip with a hollow circle.
23+
export const AdornCheckmarkEmpty: TemplateOnlyComponent<Signature> = <template>
24+
<svg
25+
viewBox='0 0 20 20'
26+
fill='none'
27+
xmlns='http://www.w3.org/2000/svg'
28+
aria-hidden='true'
29+
...attributes
30+
>
31+
<rect
32+
width='20'
33+
height='20'
34+
rx='5'
35+
fill='var(--adorn-chip-bg, var(--adorn-accent-light))'
36+
/>
37+
<circle
38+
cx='10'
39+
cy='10'
40+
r='6.5'
41+
stroke='var(--boxel-highlight-foreground)'
42+
stroke-width='1.5'
43+
/>
44+
</svg>
45+
</template>;
46+
47+
// Selected: teal rounded-square chip with a filled circle and teal check.
48+
export const AdornCheckmarkSelected: TemplateOnlyComponent<Signature> =
49+
<template>
50+
<svg
51+
viewBox='0 0 20 20'
52+
fill='none'
53+
xmlns='http://www.w3.org/2000/svg'
54+
aria-hidden='true'
55+
...attributes
56+
>
57+
<rect
58+
width='20'
59+
height='20'
60+
rx='5'
61+
fill='var(--adorn-chip-bg, var(--adorn-accent-light))'
62+
/>
63+
<circle cx='10' cy='10' r='7' fill='var(--boxel-highlight-foreground)' />
64+
<path
65+
d='M6.5 10.5L8.5 12.5L13.5 7.5'
66+
stroke='var(--adorn-accent-light)'
67+
stroke-width='1.5'
68+
stroke-linecap='round'
69+
stroke-linejoin='round'
70+
/>
71+
</svg>
72+
</template>;

packages/host/app/components/operator-mode/operator-mode-overlays.gts

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { on } from '@ember/modifier';
33
import { action } from '@ember/object';
44
import { service } from '@ember/service';
55

6-
import DotsVertical from '@cardstack/boxel-icons/dots-vertical';
76
import { modifier } from 'ember-modifier';
87
import { consume } from 'ember-provide-consume-context';
98
import { velcro } from 'ember-velcro';
@@ -12,7 +11,7 @@ import { TrackedSet } from 'tracked-built-ins';
1211
import type { BoxelDropdownAPI } from '@cardstack/boxel-ui/components';
1312
import {
1413
BoxelDropdown,
15-
IconButton,
14+
ContextButton,
1615
Menu,
1716
} from '@cardstack/boxel-ui/components';
1817

@@ -45,7 +44,10 @@ import {
4544

4645
import AdornContext from '@cardstack/host/components/adorn/adorn-context';
4746
import AdornLabel from '@cardstack/host/components/adorn/adorn-label';
48-
import AdornSelectChip from '@cardstack/host/components/adorn/adorn-select-chip';
47+
import {
48+
AdornCheckmarkEmpty,
49+
AdornCheckmarkSelected,
50+
} from '@cardstack/host/components/adorn/adorn-select-checkmark';
4951

5052
import { removeFileExtension } from '@cardstack/host/utils/card-search/types';
5153

@@ -156,10 +158,12 @@ export default class OperatorModeOverlays extends Overlays {
156158
@onClose={{this.handleMenuClose}}
157159
>
158160
<:trigger as |bindings|>
159-
<IconButton
160-
@icon={{DotsVertical}}
161-
class='overlay-label-menu'
162-
aria-label='Options'
161+
<ContextButton
162+
@icon='context-menu-vertical'
163+
@variant='ghost'
164+
@size='extra-small'
165+
@label='Options'
166+
type='button'
163167
data-test-overlay-more-options
164168
{{bindings}}
165169
{{on
@@ -182,23 +186,30 @@ export default class OperatorModeOverlays extends Overlays {
182186
</AdornLabel>
183187
{{/if}}
184188

185-
{{! Selection indicator — wrap the chip in a button so
186-
it can be clicked to toggle selection. }}
189+
{{! Selection toggle — a ContextButton in toggle mode showing
190+
the Adorn selection-chip artwork (empty vs. checked) via
191+
@icon, so it picks up the shared standard sizing and
192+
toggle (aria-pressed) chrome while keeping the chip look. }}
187193
{{#if (this.isButtonDisplayed 'select' renderedCard)}}
188-
<button
194+
<ContextButton
195+
@isToggle={{true}}
196+
@isActive={{isSelected}}
197+
@variant='ghost'
198+
@size='extra-small'
199+
@width='1.25rem'
200+
@height='1.25rem'
201+
@icon={{if
202+
isSelected
203+
AdornCheckmarkSelected
204+
AdornCheckmarkEmpty
205+
}}
206+
@label='select card'
189207
type='button'
190208
class='overlay-select-button'
191209
{{! @glint-ignore (glint thinks toggleSelect is not in this scope but it actually is - we check for it in the condition above) }}
192210
{{on 'click' (fn @toggleSelect cardDefOrId)}}
193-
aria-label='select card'
194-
aria-pressed={{if isSelected 'true' 'false'}}
195211
data-test-overlay-select={{removeFileExtension cardId}}
196-
>
197-
<AdornSelectChip
198-
@selected={{isSelected}}
199-
@compact={{isCompact}}
200-
/>
201-
</button>
212+
/>
202213
{{/if}}
203214
</div>
204215
{{/if}}
@@ -232,31 +243,15 @@ export default class OperatorModeOverlays extends Overlays {
232243
pointer-events: auto;
233244
z-index: 1;
234245
}
235-
.overlay-label-menu {
236-
width: 1.125rem;
237-
height: 1.125rem;
238-
margin-inline-start: 0;
239-
padding: 0.125rem;
240-
border-radius: 0.25rem;
241-
--icon-bg: var(--boxel-highlight-foreground);
242-
--icon-color: var(--boxel-highlight-foreground);
243-
--boxel-icon-button-width: 1.125rem;
244-
--boxel-icon-button-height: 1.125rem;
245-
}
246-
.overlay-label-menu:hover {
247-
background: rgba(0, 0, 0, 0.12);
248-
}
249-
/* Selection-toggle button: positions the AdornSelectChip in
250-
the bottom-right corner of the overlay and turns it into an
251-
interactive control. */
246+
/* The "..." menu trigger and the selection toggle are now shared
247+
ContextButtons (extra-small size, ghost variant). The menu icon
248+
inherits the teal label's highlight-foreground color and the
249+
hover/active background comes from the ghost variant, so the
250+
rules below only carry the consumer's placement concerns. */
252251
.overlay-select-button {
253252
position: absolute;
254253
bottom: 0.25rem;
255254
right: 0.25rem;
256-
padding: 0;
257-
border: none;
258-
background: none;
259-
cursor: pointer;
260255
pointer-events: auto;
261256
z-index: 1;
262257
}
@@ -266,16 +261,6 @@ export default class OperatorModeOverlays extends Overlays {
266261
.actions-overlay.field .overlay-select-button {
267262
display: none;
268263
}
269-
/* Compact-mode sizing for the operator-mode-specific elements
270-
(the menu trigger and the select button). The AdornLabel /
271-
AdornSelectChip primitives get their compact variant from
272-
the `@compact` arg we pass to each of them directly. */
273-
.actions-overlay.compact .overlay-label-menu {
274-
width: 0.875rem;
275-
height: 0.875rem;
276-
--boxel-icon-button-width: 0.875rem;
277-
--boxel-icon-button-height: 0.875rem;
278-
}
279264
.actions-overlay.compact .overlay-select-button {
280265
bottom: 0.125rem;
281266
right: 0.125rem;

0 commit comments

Comments
 (0)