290 lines
7.5 KiB
CSS
290 lines
7.5 KiB
CSS
/* Autocomplete styles */
|
||
.autocomplete {
|
||
@apply flex flex-col gap-1;
|
||
}
|
||
|
||
.autocomplete__trigger {
|
||
@apply relative isolate inline-flex min-h-9 rounded-field border bg-field px-3 py-2 text-sm text-field-foreground shadow-field outline-none select-none no-highlight;
|
||
|
||
/**
|
||
* Transitions
|
||
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
||
*/
|
||
transition:
|
||
background-color 150ms var(--ease-smooth),
|
||
border-color 150ms var(--ease-smooth),
|
||
box-shadow 150ms var(--ease-out);
|
||
@apply motion-reduce:transition-none;
|
||
|
||
/* Cursor */
|
||
cursor: var(--cursor-interactive);
|
||
|
||
border-width: var(--border-width-field);
|
||
border-color: var(--field-border);
|
||
|
||
/* Add inline-end padding when indicator is present */
|
||
&:has(.autocomplete__indicator) {
|
||
@apply pe-7;
|
||
}
|
||
|
||
/* Hover state - exclude when clear button is hovered to prevent double hover effect */
|
||
@media (hover: hover) {
|
||
&:hover:not(:has(.autocomplete__clear-button:hover)),
|
||
&[data-hovered="true"]:not(:has(.autocomplete__clear-button:hover)) {
|
||
@apply bg-field-hover;
|
||
border-color: var(--field-border-hover);
|
||
}
|
||
}
|
||
|
||
/* Focus visible state */
|
||
&:focus-visible:not(:focus),
|
||
&[data-focus-visible="true"] {
|
||
@apply status-focused;
|
||
border-color: var(--field-border-focus);
|
||
background-color: var(--field-focus);
|
||
}
|
||
|
||
/* Focus state */
|
||
&:focus,
|
||
&[data-focus="true"] {
|
||
}
|
||
|
||
/* Invalid state */
|
||
.autocomplete[data-invalid="true"] &,
|
||
.autocomplete[aria-invalid="true"] & {
|
||
@apply status-invalid-field;
|
||
background-color: var(--field-focus);
|
||
}
|
||
|
||
/* Disabled state */
|
||
&:disabled,
|
||
&[data-disabled="true"],
|
||
&[aria-disabled="true"] {
|
||
@apply status-disabled;
|
||
}
|
||
}
|
||
|
||
/* Variant property definitions */
|
||
.autocomplete--primary {
|
||
/* Default styles */
|
||
}
|
||
|
||
.autocomplete--secondary .autocomplete__trigger {
|
||
@apply shadow-none;
|
||
background-color: var(--autocomplete-trigger-bg);
|
||
|
||
--autocomplete-trigger-bg: var(--default);
|
||
--autocomplete-trigger-bg-hover: var(--default-hover);
|
||
--autocomplete-trigger-bg-focus: var(--default);
|
||
|
||
/* Exclude when clear button is hovered to prevent double hover effect */
|
||
@media (hover: hover) {
|
||
&:hover:not(:has(.autocomplete__clear-button:hover)),
|
||
&[data-hovered="true"]:not(:has(.autocomplete__clear-button:hover)) {
|
||
background-color: var(--autocomplete-trigger-bg-hover);
|
||
}
|
||
}
|
||
|
||
&:focus-visible:not(:focus),
|
||
&[data-focus-visible="true"] {
|
||
background-color: var(--autocomplete-trigger-bg-focus);
|
||
}
|
||
|
||
&:focus,
|
||
&[data-focus="true"] {
|
||
}
|
||
|
||
.autocomplete[data-invalid="true"] &,
|
||
.autocomplete[aria-invalid="true"] & {
|
||
background-color: var(--autocomplete-trigger-bg-focus);
|
||
}
|
||
}
|
||
|
||
.autocomplete__value {
|
||
@apply flex-1 text-start text-base wrap-break-word text-current sm:text-sm;
|
||
|
||
&[data-placeholder="true"] {
|
||
@apply text-field-placeholder;
|
||
}
|
||
|
||
/* Removes the list-box-item indicator element from the autocomplete value*/
|
||
[data-slot="list-box-item-indicator"] {
|
||
@apply hidden;
|
||
}
|
||
}
|
||
|
||
.autocomplete__indicator {
|
||
@apply absolute inset-y-0 end-2 my-auto flex shrink-0 items-center justify-center text-field-placeholder transition duration-150;
|
||
|
||
/* Cursor */
|
||
cursor: var(--cursor-interactive);
|
||
|
||
&[data-open="true"] {
|
||
@apply rotate-180;
|
||
}
|
||
|
||
&[data-slot="autocomplete-default-indicator"] {
|
||
@apply size-4;
|
||
}
|
||
}
|
||
|
||
/* Similar to popover content styles */
|
||
.autocomplete__popover {
|
||
@apply w-(--trigger-width) max-w-(--trigger-width) origin-(--trigger-anchor-point) scroll-py-1 overflow-hidden overscroll-contain bg-overlay p-0 pt-2 text-sm;
|
||
border-radius: min(32px, var(--radius-3xl));
|
||
box-shadow: var(--shadow-overlay);
|
||
|
||
/* Focus state */
|
||
&:focus-visible:not(:focus),
|
||
&[data-focus-visible="true"] {
|
||
@apply outline-none;
|
||
}
|
||
|
||
/* Entering animations */
|
||
&[data-entering="true"] {
|
||
@apply animate-in duration-250 ease-out-fluid fade-in-0 zoom-in-95;
|
||
|
||
/* Placement-specific translations */
|
||
&[data-placement="top"] {
|
||
@apply slide-in-from-bottom-1;
|
||
}
|
||
|
||
&[data-placement="bottom"] {
|
||
@apply slide-in-from-top-1;
|
||
}
|
||
|
||
&[data-placement="left"] {
|
||
@apply slide-in-from-right-1;
|
||
}
|
||
|
||
&[data-placement="right"] {
|
||
@apply slide-in-from-left-1;
|
||
}
|
||
}
|
||
|
||
/* Exiting animations */
|
||
&[data-exiting="true"] {
|
||
@apply animate-out duration-100 ease-out-quad zoom-out-95 fade-out;
|
||
}
|
||
|
||
&[data-exiting="true"],
|
||
&[data-entering="true"] {
|
||
@apply transition-[opacity,transform] will-change-[opacity,transform];
|
||
}
|
||
|
||
/* Arrow styling */
|
||
& [data-slot="popover-overlay-arrow"] {
|
||
fill: var(--overlay);
|
||
}
|
||
|
||
/* Arrow rotation based on placement */
|
||
&[data-placement="bottom"] [data-slot="popover-overlay-arrow"] {
|
||
rotate: 180deg;
|
||
}
|
||
|
||
&[data-placement="left"] [data-slot="popover-overlay-arrow"] {
|
||
rotate: -90deg;
|
||
}
|
||
|
||
&[data-placement="right"] [data-slot="popover-overlay-arrow"] {
|
||
rotate: 90deg;
|
||
}
|
||
|
||
/* Listbox styles */
|
||
[data-slot="list-box"] {
|
||
@apply max-h-[320px] scrollbar overflow-y-auto p-1.5 outline-none;
|
||
}
|
||
|
||
[data-slot="list-box-item"] {
|
||
@apply px-2.5;
|
||
}
|
||
|
||
/* Virtualized rows: react-aria's Virtualizer wraps each row in an absolutely-positioned
|
||
* [role="presentation"] element whose width equals the scroll container's clientWidth
|
||
* (which includes the list-box horizontal padding) while sitting inside the padded content
|
||
* box. Without this, rows overflow the right padding and the hover background reaches the
|
||
* popover edge. Subtract the horizontal padding (2 × p-1.5 = 12px) to keep rows aligned. */
|
||
[role="presentation"] > [data-slot="list-box-item"] {
|
||
width: calc(100% - var(--spacing) * 3);
|
||
}
|
||
|
||
/* As the popover closes fast in single-select mode, the default item indicator transition is not even visible - so we disable it only for single-select */
|
||
[data-slot="list-box"]:not([aria-multiselectable="true"]) [data-slot="list-box-item-indicator"],
|
||
[data-slot="list-box"]:not([aria-multiselectable="true"])
|
||
[data-slot="list-box-item-indicator"]
|
||
[data-slot="list-box-item-indicator--checkmark"] {
|
||
@apply transition-none;
|
||
}
|
||
|
||
/* Search Field styles */
|
||
[data-slot="search-field"] {
|
||
@apply px-3 py-1 outline-none;
|
||
}
|
||
|
||
/* Empty State */
|
||
[data-slot="empty-state"] {
|
||
@apply text-center text-sm text-overlay-foreground/60;
|
||
}
|
||
}
|
||
|
||
/* Popover dialog — mirrors Popover.Dialog focus management without a visible focus ring */
|
||
.autocomplete__popover-dialog {
|
||
@apply outline-none;
|
||
|
||
&:focus,
|
||
&:focus-visible,
|
||
&[data-focus-visible="true"] {
|
||
@apply outline-none;
|
||
}
|
||
}
|
||
|
||
/* Full width modifier */
|
||
.autocomplete--full-width {
|
||
@apply w-full;
|
||
}
|
||
|
||
.autocomplete__trigger--full-width {
|
||
@apply w-full;
|
||
}
|
||
|
||
/* Clear Button styles */
|
||
.autocomplete__clear-button {
|
||
@apply relative isolate inline-flex h-6 w-6 shrink-0 origin-center items-center justify-center rounded-xl p-1 text-muted select-none no-highlight;
|
||
@apply me-0 size-5 shrink-0 self-center bg-transparent;
|
||
|
||
/* Cursor */
|
||
cursor: var(--cursor-interactive);
|
||
|
||
@apply transform-gpu;
|
||
|
||
/* Transition only when visible - disappears immediately when cleared */
|
||
&:not([data-empty="true"]) {
|
||
transition: opacity 150ms var(--ease-smooth);
|
||
@apply motion-reduce:transition-none;
|
||
}
|
||
|
||
&[data-empty="true"] {
|
||
@apply pointer-events-none opacity-0;
|
||
}
|
||
|
||
[data-slot="autocomplete-clear-button-icon"] {
|
||
@apply size-3.5;
|
||
}
|
||
|
||
&:active,
|
||
&[data-pressed="true"] {
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
&:hover,
|
||
&[data-hovered="true"] {
|
||
@apply bg-default-hover;
|
||
}
|
||
}
|
||
|
||
&:active,
|
||
&[data-pressed="true"] {
|
||
transform: scale(0.93);
|
||
}
|
||
}
|