refactor(combobox): SelectedTags as band above the Control (Chakra pattern)

Switched the multi-mode chips from MUI-style "inline inside the input"
to the Chakra v3 multi-select pattern — chips render as a separated
band ABOVE the Control. Two wins:

- The dropdown popover (opens downward) never covers the selected
  chips, which sit comfortably above the input.
- The Control stays a normal-height single-line input. No layout
  shifts, no absolute-positioned affordances, no `display: contents`
  gymnastics, no input flex tuning.

Reverts the inside-the-Control CSS gymnastics from `30e9517a` /
`64bbe9b1` / `87e3d68f` (flex-wrap, padding-right reservation,
absolute clear+trigger, input flex basis). `<Combobox.SelectedTags>`
now renders as a plain `flex-wrap` row with chip gap and a margin-
block-end separating it from the Control. Demo composes
`<Combobox.SelectedTags />` BEFORE `<Combobox.Control>` when
`mode === 'multiple'`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 91b0efc461
commit 20709caf9c

@ -88,7 +88,6 @@
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: var(--_combobox-control-gap); gap: var(--_combobox-control-gap);
inline-size: 100%; inline-size: 100%;
min-inline-size: 0; min-inline-size: 0;
@ -165,11 +164,9 @@
[data-combobox-input] { [data-combobox-input] {
box-sizing: border-box; box-sizing: border-box;
/* Basis `4rem` lets the input share a wrapped row with chips + flex: 1 1 auto;
* clear/trigger affordances while still growing to fill any remaining inline-size: 100%;
* space. Pure `inline-size: 100%` forces a new line under flex-wrap. */ min-inline-size: 0;
flex: 1 1 4rem;
min-inline-size: 4rem;
border: 0; border: 0;
background: transparent; background: transparent;
color: inherit; color: inherit;
@ -493,59 +490,25 @@
} }
} }
/* SelectedTags — logical group for the chip strip. Uses /* SelectedTags — chip strip rendered ABOVE the Control so the
* `display: contents` so each chip becomes a direct flex child of * dropdown popover (which opens downward) never covers the selected
* `<Combobox.Control>` instead of being nested in its own row. This * values. Chakra v3 multi-select pattern: clean separated band, the
* lets the input flow on the same wrapped row as the last chip * Control stays plain `<input>` height with no layout shift. */
* (Material UI Autocomplete behaviour) rather than always dropping
* to a fresh row below the whole tag block. */
[data-combobox-selected-tags] { [data-combobox-selected-tags] {
display: contents; display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-1-5);
margin-block-end: var(--space-2);
min-inline-size: 0;
} }
/* Empty state — element stays in DOM (so `:has(...:not([data-empty]))` /* Hide entirely when no chips so the Control isn't pushed down by an
* still works for layout siblings) but no children render. */ * empty band. */
[data-combobox-selected-tags][data-empty] { [data-combobox-selected-tags][data-empty] {
display: none; display: none;
} }
/* Material UI Autocomplete pattern — when chips are present, pin the
* right-side affordances absolutely so chips + input wrap freely
* without ever pushing the clear/trigger to a new row.
*
* `--_combobox-affordances-w` reserves the right-padding the
* absolutely-positioned ClearButton + Trigger consume (each is ~26px
* wide with size=md, scaled below for xs/sm/lg/xl). */
[data-combobox-control]:has([data-combobox-selected-tags]:not([data-empty])) {
--_combobox-affordances-w: calc(var(--_combobox-control-height) * 2);
position: relative;
padding-block: var(--space-1);
padding-inline-end: calc(var(--_combobox-control-px) + var(--_combobox-affordances-w));
}
[data-combobox-control]:has([data-combobox-selected-tags]:not([data-empty]))
[data-combobox-clear-button],
[data-combobox-control]:has([data-combobox-selected-tags]:not([data-empty]))
[data-combobox-trigger] {
position: absolute;
/* Pin to the FIRST row of the wrapped control so the affordances stay
* aligned with the input + chip row even when many chips wrap into
* extra rows below. `padding-block` (var(--space-1)) is the control's
* top offset; matching that here keeps the buttons flush with the
* first chip / input row. */
inset-block-start: var(--space-1);
}
[data-combobox-control]:has([data-combobox-selected-tags]:not([data-empty]))
[data-combobox-clear-button] {
inset-inline-end: calc(var(--_combobox-control-px) + var(--_combobox-control-height));
}
[data-combobox-control]:has([data-combobox-selected-tags]:not([data-empty]))
[data-combobox-trigger] {
inset-inline-end: var(--_combobox-control-px);
}
[data-combobox-selected-tag] { [data-combobox-selected-tag] {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

@ -265,10 +265,10 @@
{color} {color}
name="framework" name="framework"
> >
{#if mode === 'multiple'}
<Combobox.SelectedTags />
{/if}
<Combobox.Control> <Combobox.Control>
{#if mode === 'multiple'}
<Combobox.SelectedTags />
{/if}
<Combobox.Input <Combobox.Input
placeholder="Search framework..." placeholder="Search framework..."
data-perm-step={!disabled ? '0' : undefined} data-perm-step={!disabled ? '0' : undefined}

Loading…
Cancel
Save

Powered by TurnKey Linux.