Skip to content

Commit 07154c7

Browse files
feat(mosaic): add Menu.Indicator trailing slot for the action menu spinner
1 parent 7993987 commit 07154c7

5 files changed

Lines changed: 59 additions & 17 deletions

File tree

‎packages/mosaic/src/components/action-menu/action-menu.tsx‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,17 @@ export function ActionMenu({ label, actions, triggerRef, disabled, focusableWhen
7676
</Menu.Media>
7777
) : null}
7878
<Menu.Label>{action.label}</Menu.Label>
79-
{action.progress === 'pending' && showSpinner ? (
80-
<Spinner
81-
size='sm'
82-
role='progressbar'
83-
aria-hidden={undefined}
84-
aria-label={action.label}
85-
/>
79+
{action.progress !== undefined ? (
80+
<Menu.Indicator>
81+
{action.progress === 'pending' && showSpinner ? (
82+
<Spinner
83+
size='sm'
84+
role='progressbar'
85+
aria-hidden={undefined}
86+
aria-label={action.label}
87+
/>
88+
) : null}
89+
</Menu.Indicator>
8690
) : null}
8791
</Menu.Item>
8892
))}

‎packages/mosaic/src/components/menu/menu.styles.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,17 @@ export const label = stylex.create({
160160
},
161161
});
162162

163+
export const indicator = stylex.create({
164+
base: {
165+
alignItems: 'center',
166+
aspectRatio: '1/1',
167+
display: 'flex',
168+
flexShrink: 0,
169+
justifyContent: 'center',
170+
width: space['4'],
171+
},
172+
});
173+
163174
export const separator = stylex.create({
164175
base: {
165176
// Full-bleed across the popup: cancel the viewport's inline padding.

‎packages/mosaic/src/components/menu/menu.tsx‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,27 @@ export const MenuLabel = React.forwardRef<HTMLSpanElement, MenuLabelProps>(funct
167167
});
168168
});
169169

170+
export type MenuIndicatorProps = MosaicComponentProps<'span'>;
171+
172+
export const MenuIndicator = React.forwardRef<HTMLSpanElement, MenuIndicatorProps>(function MosaicMenuIndicator(
173+
{ render, xstyle, ...rest },
174+
ref,
175+
) {
176+
return useRender({
177+
defaultTagName: 'span',
178+
render,
179+
ref,
180+
props: {
181+
...mergeStyleProps(themeProps('menu-indicator'), stylex.props(reset.base, slots.indicator.base, xstyle), rest),
182+
},
183+
});
184+
});
185+
170186
export interface MenuItemProps extends Omit<PrimitiveMenuItemProps, 'className' | 'style'>, MosaicStyleProps {
171187
/** Semantic color of the action. */
172188
color?: 'neutral' | 'negative';
173189
/**
174-
* The row's content, composed from `Menu.Media` and `Menu.Label`. Required, and text goes in
190+
* The row's content, composed from `Menu.Media`, `Menu.Label` and `Menu.Indicator`. Required, and text goes in
175191
* `Menu.Label` rather than straight in here: a bare text node is not a flex item the row can
176192
* size, so it neither lines up with the other rows nor truncates.
177193
*/
@@ -227,5 +243,6 @@ export const Menu = {
227243
Item: MenuItem,
228244
Media: MenuMedia,
229245
Label: MenuLabel,
246+
Indicator: MenuIndicator,
230247
Separator: MenuSeparator,
231248
};

‎packages/swingset/src/stories/menu.component.mdx‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ props (ARIA attributes, click and keyboard handlers) to spread.
7070
### Items
7171

7272
`label` names the item for typeahead and for assistive technology. What the row shows is composed
73-
from `Menu.Media` and `Menu.Label`, and those children are required: text dropped straight into the
73+
from `Menu.Media`, `Menu.Label` and `Menu.Indicator`, and those children are required: text dropped straight into the
7474
item is not a flex child the row can size, so it neither lines up with the other rows nor
7575
truncates. A row with nothing to lead it is `Menu.Label` alone. Use
7676
`color='negative'` for destructive actions; the color is inherited by the children. `disabled` items
@@ -121,7 +121,13 @@ line. Items whose text is a name — an account, an organization — need it mos
121121
the menu instead of ellipsing. It is also what pushes a trailing mark to the end of the row. Use it
122122
for every composed item, so rows built from parts all read the same.
123123

124-
The two together are the whole of a row that leads with an avatar and trails with a check:
124+
### Indicator
125+
126+
`Menu.Indicator` is a square trailing column for a mark at the end of the row, such as a check or a
127+
spinner. It keeps its width while empty, so render it on any item whose mark can appear later and
128+
the label does not shift when it does.
129+
130+
Together they make a row that leads with an avatar and trails with a check:
125131

126132
```tsx
127133
<Menu.Item label='colin@clerk.dev'>
@@ -134,7 +140,9 @@ The two together are the whole of a row that leads with an avatar and trails wit
134140
</Avatar.Root>
135141
</Menu.Media>
136142
<Menu.Label>colin@clerk.dev</Menu.Label>
137-
<Icon glyph={Checkmark} size='sm' />
143+
<Menu.Indicator>
144+
<Icon glyph={Checkmark} size='sm' />
145+
</Menu.Indicator>
138146
</Menu.Item>
139147
```
140148

‎packages/swingset/src/stories/menu.component.stories.tsx‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,14 @@ export function Accounts() {
7878
</Avatar.Root>
7979
</Menu.Media>
8080
<Menu.Label>{account.identifier}</Menu.Label>
81-
{account.active ? (
82-
<Icon
83-
glyph={Checkmark}
84-
size='sm'
85-
/>
86-
) : null}
81+
<Menu.Indicator>
82+
{account.active ? (
83+
<Icon
84+
glyph={Checkmark}
85+
size='sm'
86+
/>
87+
) : null}
88+
</Menu.Indicator>
8789
</Menu.Item>
8890
))}
8991
</Menu.Popup>

0 commit comments

Comments
 (0)