Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .changeset/command-palette-item-disabled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] Keep standalone command-palette item IDs on the rendered option and suppress consumer click callbacks when an item is disabled.

@cixzhang @korkt-kim
5 changes: 5 additions & 0 deletions apps/storybook/rtl-audit/targets.json
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,11 @@
"next": ".astryx-avatar-group-overflow"
}
},
{
"component": "CommandPaletteItem",
"storyId": "core-commandpaletteitem--states",
"dims": []
},
{
"component": "CommandPaletteFooter",
"storyId": "core-commandpalettefooter--default",
Expand Down
9 changes: 9 additions & 0 deletions apps/storybook/rtl-audit/verified-not-applicable.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,15 @@
"packages/core/src/Divider/Divider.tsx": "sha256:f302b5c9eecde40b486c09d49c71cb7dab158e2fcedcb38c0145671c45d9cf8d"
}
},
{
"component": "core/CommandPaletteItem",
"reason": "CommandPaletteItem owns one inline flex option row with logical inline padding, logical start text alignment, and direction-neutral highlight, selection, disabled, and press paint. Its supported content is non-interactive text, icons, descriptions, and shortcut hints; it owns no directional glyph, side placement, reordering, horizontal scroll, drag, overlay, or horizontal keyboard behavior. The dedicated state fixture exercises every component-owned visual state without adding an RTL-specific dimension.",
"evidence": {
"apps/storybook/stories/CommandPaletteItem.stories.tsx": "sha256:9d5c2229f89da3ac53347078560b9ad57cfff7b20a3c71ad3fab2c338161dbf1",
"packages/core/src/CommandPalette/CommandPaletteItem.tsx": "sha256:aee0fcaa7b9829e04317f1cab9708c045a1da0d9180e490d64c6a576c39d8f42",
"packages/core/src/CommandPalette/index.ts": "sha256:ba38845bbde6d73172df2c0791ca513c9f2b7baab17440e066e783ff6cfc7558"
}
},
{
"component": "core/CommandPaletteGroup",
"reason": "CommandPaletteGroup owns a vertically stacked listbox group with logical inline padding and block spacing. Its visible heading and item content follow normal document direction; it renders no directional glyph, physical inline positioning, reordered controls, horizontal scrolling or dragging, side-placed overlay, or horizontal keyboard behavior. The dedicated fixtures exercise the supported composition and public heading theme target without adding a component-owned RTL dimension.",
Expand Down
46 changes: 46 additions & 0 deletions apps/storybook/stories/CommandPaletteItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

import type {Meta, StoryObj} from '@storybook/react';
import {
CommandPaletteItem,
CommandPaletteList,
} from '@astryxdesign/core/CommandPalette';
import {Kbd} from '@astryxdesign/core/Kbd';

const meta: Meta<typeof CommandPaletteItem> = {
title: 'Core/CommandPaletteItem',
component: CommandPaletteItem,
tags: ['autodocs', 'visual-theme-matrix'],
parameters: {
layout: 'centered',
},
};

export default meta;
type Story = StoryObj<typeof meta>;

/** Default, highlighted, selected, and disabled states in the supported listbox composition. */
export const States: Story = {
render: () => (
<CommandPaletteList
label="Command item states"
style={{inlineSize: 'min(22rem, calc(100vw - 2rem))'}}>
<CommandPaletteItem value="open-file" onSelect={() => {}}>
Open file
<Kbd keys="mod+o" />
</CommandPaletteItem>
<CommandPaletteItem
value="search-files"
isHighlighted
onSelect={() => {}}>
Search files
</CommandPaletteItem>
<CommandPaletteItem value="settings" isSelected onSelect={() => {}}>
Settings
</CommandPaletteItem>
<CommandPaletteItem value="archived" isDisabled onSelect={() => {}}>
Archived command
</CommandPaletteItem>
</CommandPaletteList>
),
};
117 changes: 95 additions & 22 deletions packages/core/src/CommandPalette/CommandPalette.doc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export const docs = {
'dialog',
'navigation',
],
description: 'Root component. Manages open state, search, keyboard navigation, and composition slots.',
description:
'Root component. Manages open state, search, keyboard navigation, and composition slots.',
// Intentionally a contained isInline preview, not playground.overlay: the
// component stays visible on load and knobs stay live, whereas a real
// showModal() overlay makes the page inert — see ComponentPlaygroundConfig.overlay
Expand Down Expand Up @@ -116,13 +117,15 @@ export const docs = {
{
name: 'searchSource',
type: 'SearchSource<T>',
description: 'Search source providing items via search(query) and bootstrap(). Use createStaticSource for static lists.',
description:
'Search source providing items via search(query) and bootstrap(). Use createStaticSource for static lists.',
required: true,
},
{
name: 'input',
type: 'ReactNode',
description: 'Input slot. Defaults to CommandPaletteInput with standard behavior.',
description:
'Input slot. Defaults to CommandPaletteInput with standard behavior.',
default: '<CommandPaletteInput />',
slotElements: [
{
Expand All @@ -137,7 +140,8 @@ export const docs = {
{
name: 'footer',
type: 'ReactNode',
description: 'Footer slot. Defaults to CommandPaletteFooter showing keyboard hints.',
description:
'Footer slot. Defaults to CommandPaletteFooter showing keyboard hints.',
default: '<CommandPaletteFooter />',
slotElements: [
{
Expand All @@ -152,7 +156,8 @@ export const docs = {
{
name: 'renderItem',
type: '(item: T, isSelected: boolean) => ReactNode',
description: 'Per-item render function. Auto-grouping by auxiliaryData.group is preserved. When omitted, renders label text.',
description:
'Per-item render function for non-interactive presentation content such as labels, icons, descriptions, and shortcut hints. Do not return buttons or links inside the option. Auto-grouping by auxiliaryData.group is preserved. When omitted, renders label text.',
},
{
name: 'emptySearchText',
Expand All @@ -163,7 +168,8 @@ export const docs = {
{
name: 'emptyBootstrapText',
type: 'ReactNode',
description: 'Content shown when there is no search query and bootstrap() returns nothing.',
description:
'Content shown when there is no search query and bootstrap() returns nothing.',
default: "'Type to search'",
},
{
Expand Down Expand Up @@ -197,7 +203,8 @@ export const docs = {
{
name: 'isInline',
type: 'boolean',
description: 'Renders command palette content inline without modal behavior. Automatically disables input auto-focus and initial highlighted-item auto-scroll. For documentation previews and showcases only.',
description:
'Renders command palette content inline without modal behavior. Automatically disables input auto-focus and initial highlighted-item auto-scroll. For documentation previews and showcases only.',
default: 'false',
},
],
Expand All @@ -222,12 +229,34 @@ export const docs = {
},
usage: {
anatomy,
description: 'CommandPalette is a searchable dialog for quick access to commands, navigation, and actions. Use it as a keyboard-driven launcher powered by SearchSource for filtering and selection.',
description:
'CommandPalette is a searchable dialog for quick access to commands, navigation, and actions. Use it as a keyboard-driven launcher powered by SearchSource for filtering and selection.',
bestPractices: [
{ guidance: true, description: 'Provide a searchSource with bootstrap results so users see useful options before typing.' },
{ guidance: true, description: 'Use auxiliaryData.group on items to automatically organize results into labeled sections.' },
{ guidance: false, description: 'Use CommandPalette for simple dropdowns or menus; use Menu or Selector for inline selections.' },
{ guidance: false, description: 'Add too many groups or items; curate results to keep the palette fast and scannable.' },
{
guidance: true,
description:
'Provide a searchSource with bootstrap results so users see useful options before typing.',
},
{
guidance: true,
description:
'Use auxiliaryData.group on items to automatically organize results into labeled sections.',
},
{
guidance: true,
description:
'Keep renderItem content non-interactive; each rendered row is one option, so buttons and links cannot be nested inside it.',
},
{
guidance: false,
description:
'Use CommandPalette for simple dropdowns or menus; use Menu or Selector for inline selections.',
},
{
guidance: false,
description:
'Add too many groups or items; curate results to keep the palette fast and scannable.',
},
],
},
};
Expand All @@ -236,12 +265,34 @@ export const docs = {
export const docsZh = {
usage: {
anatomy,
description: 'CommandPalette is a searchable dialog for quick access to commands, navigation, and actions. Use it as a keyboard-driven launcher powered by SearchSource for filtering and selection.',
description:
'CommandPalette is a searchable dialog for quick access to commands, navigation, and actions. Use it as a keyboard-driven launcher powered by SearchSource for filtering and selection.',
bestPractices: [
{ guidance: true, description: 'Provide a searchSource with bootstrap results so users see useful options before typing.' },
{ guidance: true, description: 'Use auxiliaryData.group on items to automatically organize results into labeled sections.' },
{ guidance: false, description: 'Use CommandPalette for simple dropdowns or menus; use Menu or Selector for inline selections.' },
{ guidance: false, description: 'Add too many groups or items; curate results to keep the palette fast and scannable.' },
{
guidance: true,
description:
'Provide a searchSource with bootstrap results so users see useful options before typing.',
},
{
guidance: true,
description:
'Use auxiliaryData.group on items to automatically organize results into labeled sections.',
},
{
guidance: true,
description:
'Keep renderItem content non-interactive; each rendered row is one option, so buttons and links cannot be nested inside it.',
},
{
guidance: false,
description:
'Use CommandPalette for simple dropdowns or menus; use Menu or Selector for inline selections.',
},
{
guidance: false,
description:
'Add too many groups or items; curate results to keep the palette fast and scannable.',
},
],
},
};
Expand All @@ -252,12 +303,34 @@ export const docsDense = {
'searchSource-driven command palette dialog; filtering, keyboard nav, grouping, selection; same SearchSource interface as Typeahead',
usage: {
anatomy,
description: 'CommandPalette is a searchable dialog for quick access to commands, navigation, and actions. Use it as a keyboard-driven launcher powered by SearchSource for filtering and selection.',
description:
'CommandPalette is a searchable dialog for quick access to commands, navigation, and actions. Use it as a keyboard-driven launcher powered by SearchSource for filtering and selection.',
bestPractices: [
{ guidance: true, description: 'Provide a searchSource with bootstrap results so users see useful options before typing.' },
{ guidance: true, description: 'Use auxiliaryData.group on items to automatically organize results into labeled sections.' },
{ guidance: false, description: 'Use CommandPalette for simple dropdowns or menus; use Menu or Selector for inline selections.' },
{ guidance: false, description: 'Add too many groups or items; curate results to keep the palette fast and scannable.' },
{
guidance: true,
description:
'Provide a searchSource with bootstrap results so users see useful options before typing.',
},
{
guidance: true,
description:
'Use auxiliaryData.group on items to automatically organize results into labeled sections.',
},
{
guidance: true,
description:
'Keep renderItem content non-interactive; each rendered row is one option, so buttons and links cannot be nested inside it.',
},
{
guidance: false,
description:
'Use CommandPalette for simple dropdowns or menus; use Menu or Selector for inline selections.',
},
{
guidance: false,
description:
'Add too many groups or items; curate results to keep the palette fast and scannable.',
},
],
},
};
6 changes: 4 additions & 2 deletions packages/core/src/CommandPalette/CommandPalette.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ Consumer migration instructions belong in consumer docs and release notes.

**Owns**

- Search orchestration and the current Input, List, Item, Group, Group heading,
Empty, and Footer parts and targets.
- Search orchestration and composition of the current Input, List, Item, Group,
Group heading, Empty, and Footer parts and targets.
- The native Query field rendered inside the default Input.

**Does not own / non-goals**
Expand All @@ -67,6 +67,8 @@ Consumer migration instructions belong in consumer docs and release notes.
badges — owned by their respective components.
- Caller-provided replacement Input, Footer, item content, or trailing input
content — owned by the product callsite.
- The Item option's passthrough, pointer, disabled, selected, highlighted, and
standalone-scroll semantics — owned by `component:CommandPaletteItem`.
- A `command-palette` root target — no such current target exists, and this
factual backfill does not invent one.
- Shared layer hosting, positioning, or lifecycle rules beyond the current
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/CommandPalette/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ export interface CommandPaletteProps<

/**
* Per-item render function. Receives the item and whether it is currently selected.
* Auto-grouping by `auxiliaryData.group` is preserved.
* Return non-interactive presentation content; buttons and links cannot be
* nested inside the rendered option. Auto-grouping by `auxiliaryData.group`
* is preserved.
* When omitted, renders each item's `label` text.
*/
renderItem?: (item: T, isSelected: boolean) => ReactNode;
Expand Down
Loading
Loading