diff --git a/package.json b/package.json index 4e88ad8cb4c..a7c7cb4547a 100644 --- a/package.json +++ b/package.json @@ -238,6 +238,7 @@ "v-code-diff": "patches/v-code-diff.patch", "vuedraggable@4.1.0": "patches/vuedraggable@4.1.0.patch", "assert@2.1.0": "patches/assert@2.1.0.patch", + "reka-ui@2.5.0": "patches/reka-ui@2.5.0.patch", "lodash@4.18.1": "patches/lodash@4.18.1.patch", "@langchain/openai@1.4.4": "patches/@langchain__openai@1.4.4.patch", "@langchain/google-common@2.1.24": "patches/@langchain__google-common@2.1.24.patch", diff --git a/packages/frontend/@n8n/design-system/src/components/N8nDropdownMenu/DropdownMenuItem.vue b/packages/frontend/@n8n/design-system/src/components/N8nDropdownMenu/DropdownMenuItem.vue index fcabc045834..3819c83f418 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nDropdownMenu/DropdownMenuItem.vue +++ b/packages/frontend/@n8n/design-system/src/components/N8nDropdownMenu/DropdownMenuItem.vue @@ -423,6 +423,7 @@ onBeforeUnmount(() => { diff --git a/packages/frontend/@n8n/design-system/src/v2/components/Combobox/ComboboxItem.vue b/packages/frontend/@n8n/design-system/src/v2/components/Combobox/ComboboxItem.vue new file mode 100644 index 00000000000..8fc87d8cd13 --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/v2/components/Combobox/ComboboxItem.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/packages/frontend/@n8n/design-system/src/v2/components/Combobox/component-combobox.md b/packages/frontend/@n8n/design-system/src/v2/components/Combobox/component-combobox.md new file mode 100644 index 00000000000..e3348e7c3b0 --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/v2/components/Combobox/component-combobox.md @@ -0,0 +1,389 @@ +# Component specification + +Allows users to search and choose one or more options from a list. All comboboxes filter as the user types and open the dropdown on input focus. Supports single and multiple selection, grouped lists (groups and separators), and is suited to larger datasets where a plain Select is not enough. + +- **Component Name:** N8nCombobox2 +- **Figma Component:** [ComboBox](https://www.figma.com/design/8zib7Trf2D2CHYXrEGPHkg/n8n-Design-System-V3?node-id=2631-7139&m=dev) +- **Reka UI Component:** [Combobox](https://reka-ui.com/docs/components/combobox) + +## Component structure + +``` +Combobox (N8nCombobox2) +├── ComboboxAnchor (trigger chrome) +│ ├── Single: leading icon + ComboboxInput + clear + chevron +│ └── Multiple: N8nTagsInput2 (embedded) + clear + chevron +│ └── #input slot: ComboboxInput as-child → TagsInputInput +└── ComboboxContent (portaled dropdown, role="listbox", max-height 500px by default) + └── ComboboxViewport (scrollable list area) + ├── ComboboxEmpty + └── ComboboxGroup (one per `type: 'group'`, or batched top-level options) + ├── ComboboxLabel (optional section heading) + └── N8nCombobox2Item (reka-ui ComboboxItem + default row: icon, label, check) +``` + +There are no header/footer slots. Reka applies `role="listbox"` to `ComboboxContent`, so non-option content (buttons, links, freeform markup) would break the ARIA listbox pattern. For section headings use `type: 'group'` with an optional `label`; for actionable chrome (e.g. “Create new…”) add a selectable option — see [Header and footer actions](#header-and-footer-actions). + +When `multiple` is true, selected values render via embedded `N8nTagsInput2` (shared chip/layout styles; Combobox keeps the field chrome). Freeform tag creation is disabled — values are only added from the dropdown. + +Use `type: 'group'` for sections. The `label` is optional. Each group maps to its own reka `ComboboxGroup`, so heading DOM `id`s stay unique and `aria-labelledby` points at the correct label. Top-level options (outside any group) are batched into an unlabeled group. Dividers are CSS between consecutive *visible* groups (`:not([hidden]) ~ :not([hidden])`), so they disappear when Reka hides a group that has no filter matches. `{ type: 'separator' }` is only a split marker for unlabeled options — it is not rendered as a DOM node (Reka would not filter a separator element). + +## Public API Definition + +**Props** + +- `id?: string` — Applied to the combobox input. Falls back to a generated id when omitted so the field always has an `id` for label association and browser a11y checks +- `name?: string` — Form field name passed to reka-ui root +- `placeholder?: string` — Shown in the input when no value is selected | `default: t('combobox.placeholder')` +- `emptyText?: string` — Shown when filtering returns no matches | `default: t('combobox.emptyText')` +- `autoFocus?: boolean` — Focus the input on mount +- `items?: ComboboxItem[]` — Array of options to render (see [Item shapes](#item-shapes) below). If `modelValue` is set before items resolve, the input shows the raw value until a matching item is available, then updates to that item's `label`. There is no built-in pending/skeleton state — wrap the field at the call site if you need one. +- `defaultValue?: ComboboxValue | ComboboxValue[]` — Initial value when uncontrolled. Seeds the selection used for tags, the clear button, leading icon, and `data-empty` (same as a controlled `modelValue`). +- `modelValue?: ComboboxValue | ComboboxValue[]` — Controlled value. Bind with `v-model` +- `multiple?: boolean` — Allow selecting multiple options. Selected values render as removable tags via embedded `N8nTagsInput2` (search input stays empty; filtering still works while typing) +- `open?: boolean` — Controlled open state. Bind with `v-model:open` +- `defaultOpen?: boolean` — Initial open state when uncontrolled +- `disabled?: boolean` — Disable interaction +- `required?: boolean` — Mark the field as required (reka-ui root) +- `icon?: IconName` — Leading icon in the trigger when no selected item icon is available +- `ignoreFilter?: boolean` — Disable built-in filtering +- `resetSearchTermOnBlur?: boolean` — Reset search text on blur | reka default: `true` +- `resetSearchTermOnSelect?: boolean` — Reset search text on select | reka default: `true` +- `openOnClick?: boolean` — Open dropdown when input is clicked | reka default: `false` +- `highlightOnHover?: boolean` — Highlight items on hover (reka-ui root) +- `clearable?: boolean` — When `true`, shows a clear button (×) when a value is selected. Hidden when disabled or empty. Default: `false` +- `teleported?: boolean` — Whether to teleport the dropdown to body. Default: `true` +- `portalTarget?: string | HTMLElement` — Portal target element (e.g. pop-out window's `document.body`). When set, portals content to the specified element. + +**UI Props** + +- `size?`: `'mini' | 'small' | 'medium' | 'large' | 'xlarge'` | `default: 'large'` — Applies to the trigger/input only. Dropdown items use a fixed size aligned with `DropdownMenu`. +- `side?`: `'top' | 'right' | 'bottom' | 'left'` | `default: 'bottom'` +- `sideOffset?`: `number` | `default: 4` +- `align?`: `'start' | 'center' | 'end'` | `default: 'start'` +- `contentClass?`: `string` — Additional class(es) on the portaled dropdown content + +The dropdown content defaults to a max height of **500px** with vertical scrolling on the viewport. Override by setting the CSS variable on a custom class: + +```css +.my-combobox-content { + --combobox-content--max-height: 300px; +} +``` + +**Events** + +- `update:modelValue(value: ComboboxValue | ComboboxValue[])` — For single selection, clearing emits `undefined`. For multiple selection, clearing emits `[]`. +- `update:open(value: boolean)` +- `highlight(payload: { ref: HTMLElement; value: ComboboxValue } | undefined)` — reka-ui root + +**Exposed** + +- `anchorRef` — Ref to the `ComboboxAnchor` element + +**Attributes** + +- `aria-*` fallthrough attributes are forwarded to the combobox input. The placeholder is never used as an accessible name — associate a `