Skip to content

Commit

Permalink
✨ Ny komponent: Combobox (#1868)
Browse files Browse the repository at this point in the history
✨Ny komponent: Combobox 🎉 

Combobox er en ny komponent som kombinerer tekstfelt og nedtrekksliste. 
- Velg mellom predefinerte alternativer i nedtrekkslisten
- Bruk input-feltet som filter for alternativene i nedtrekkslisten.
- Legg inn nye svar, hvis svaret du ikke finnes i listen
- Velg mellom SingleSelect eller MultiSelect-versjon for henholdsvis ett og flere valg. 
- Autocomplete svar i inputfeltet når man velger SingleSelect

Dokumentasjonen finner du på https://aksel.nav.no/komponenter/core/combobox
  • Loading branch information
larseirikhansen authored Jul 13, 2023
1 parent ac0fd79 commit e3027cc
Show file tree
Hide file tree
Showing 36 changed files with 2,732 additions and 154 deletions.
7 changes: 7 additions & 0 deletions .changeset/spotty-onions-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@navikt/ds-css": minor
"@navikt/ds-react": minor
"aksel.nav.no": minor
---

Combobox
5 changes: 5 additions & 0 deletions @navikt/core/css/config/_mappings.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ const StyleMappings = {
{ component: "Checkbox", main: formCss, dependencies: [typoCss] },
{ component: "CheckboxGroup", main: formCss, dependencies: [typoCss] },
{ component: "Chips", main: "chips.css", dependencies: [typoCss] },
{
component: "UNSAFE_Combobox",
main: "combobox.css",
dependencies: [typoCss],
},
{
component: "ConfirmationPanel",
main: formCss,
Expand Down
287 changes: 287 additions & 0 deletions @navikt/core/css/form/combobox.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
.navds-combobox__wrapper {
display: flex;
flex-direction: column;
width: 100%;
position: relative;
}

.navds-combobox__wrapper-inner {
position: relative;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
border: 1px solid black;
}

.navds-combobox__wrapper-inner > :first-child {
flex: 2;
}

.navds-combobox__wrapper-inner > :last-child {
display: flex;
flex-flow: row nowrap;
}

.navds-form-field--small .navds-combobox__wrapper-inner {
padding: var(--a-spacing-1) calc(var(--a-spacing-3) / 2);
}

.navds-combobox__wrapper-inner:hover {
cursor: text;
}

.navds-combobox__selected-options {
gap: var(--a-spacing-2);
}

.navds-combobox__selected-options > li {
margin: auto 0;
border-radius: var(--a-border-radius-full);
}

.navds-combobox__selected-options > li:last-of-type {
display: flex;
flex: 1;
}

.navds-combobox__selected-options--no-bg {
font-family: inherit;
font-size: var(--a-font-size-large);
font-weight: var(--a-font-weight-regular);
letter-spacing: 0;
line-height: var(--a-font-line-height-large);
margin: 0;
padding-left: calc(0.5rem - 4px);
}

.navds-combobox__input-wrapper {
width: 100%;
}

.navds-combobox__input {
z-index: 1;
flex: 1;
border: none;
padding: 0;
margin: 0;
min-width: 10ch;
width: 100%;
}

.navds-combobox__input:focus-visible {
outline: none;
border: none;
box-shadow: none;
}

.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus-visible) {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
}

.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus-visible).navds-combobox__wrapper-inner--virtually-unfocused {
box-shadow: none;
}

@supports not selector(:focus-visible) {
.navds-combobox__input:focus-visible {
outline: none;
border: none;
box-shadow: none;
}

.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus) {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
}

.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus).navds-combobox__wrapper-inner--virtually-unfocused {
box-shadow: none;
}
}

.navds-combobox__button-clear {
border-radius: var(--a-border-radius-medium);
color: var(--ac-combobox-clear-icon, var(--a-text-subtle));
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background: none;
border: none;
font-size: 1rem;
padding: 0;
}

.navds-combobox__button-clear svg {
width: 1.5rem;
height: 1.5rem;
}

.navds-combobox__input::-webkit-search-cancel-button {
display: none;
}

.navds-combobox__button-toggle-list {
border-radius: var(--a-border-radius-medium);
color: var(--a-text-default);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background: none;
border: none;
font-size: 1rem;
padding: 0;
}

.navds-combobox__button-clear:active:hover,
.navds-combobox__button-toggle-list:active:hover {
color: var(--ac-combobox-clear-icon-active, var(--a-text-action));
}

.navds-combobox__button-clear:hover,
.navds-combobox__button-toggle-list:hover {
color: var(--ac-combobox-clear-icon-hover, var(--a-text-action-selected));
}

.navds-combobox__button-toggle-list:focus-visible {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
outline: none;
}

@supports not selector(:focus-visible) {
.navds-combobox__button-toggle-list:focus {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
outline: none;
}
}

.navds-combobox__button-toggle-list svg {
width: 1.5rem;
height: 1.5rem;
pointer-events: none;
}

.navds-form-field--small .navds-combobox__button-toggle-list svg,
.navds-form-field--small .navds-combobox__button-clear svg {
width: 1.25rem;
height: 1.25rem;
}

/* dropdown list */

.navds-combobox__list {
max-height: 290px;
overflow-y: auto;
position: absolute;
left: 0;
right: 0;
z-index: 9999;
top: calc(100% + var(--a-spacing-2));
list-style: none;
margin: 0;
border: 1px solid var(--ac-combobox-list-border-color, var(--a-border-divider));
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 0;
box-shadow: var(--a-shadow-small);
border-radius: 4px;
gap: 4px 0;
background-color: var(--ac-combobox-list-bg, var(--a-surface-default));
color: var(--ac-combobox-list-text, var(--a-text-default));
}

.navds-combobox__list--closed {
display: none;
}

.navds-combobox__list svg {
height: 1.5rem;
width: 1.5rem;
}

.navds-combobox__list-item,
.navds-combobox__list-item__no-options,
.navds-combobox__list-item__new-option {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: var(--a-spacing-3);
width: 100%;
background-color: var(--ac-combobox-list-item-bg, var(--a-surface-default));
}

.navds-form-field--small .navds-combobox__list-item,
.navds-form-field--small .navds-combobox__list-item__no-options,
.navds-form-field--small .navds-combobox__list-item__new-option {
padding: calc(var(--a-spacing-3) / 2) var(--a-spacing-2);
}

.navds-combobox__list-item--loading {
display: flex;
justify-content: center;
padding: var(--a-spacing-3);
background-color: var(--ac-combobox-list-item-loading-bg, var(--a-surface-default));
width: 100%;
}

.navds-combobox__list-item--focus,
.navds-combobox__list-item:hover {
background-color: var(--ac-combobox-list-item-hover-bg, var(--a-surface-hover));
cursor: pointer;
border-left: 4px solid var(--ac-combobox-list-item-hover-border-left, var(--a-border-strong));
padding-left: calc(var(--a-spacing-3) - 4px);
}

.navds-combobox__list-item--selected {
background-color: var(--ac-combobox-list-item-selected-bg, var(--a-surface-selected));
}

.navds-combobox__list-item--selected.navds-combobox__list-item--focus,
.navds-combobox__list-item--selected:hover {
background-color: var(--ac-combobox-list-item-selected-hover-bg, var(--a-surface-action-subtle-hover));
border-left: 4px solid var(--ac-combobox-list-item-selected-hover-border-left, var(--a-border-focus));
padding-left: calc(var(--a-spacing-3) - 4px);
}

.navds-combobox__list-item__new-option {
border-bottom: 1px solid var(--a-border-divider);
background: var(--a-surface-neutral-subtle);
cursor: pointer;
justify-content: flex-start;
gap: 0.25rem;
}

.navds-combobox__list-item__new-option:hover {
border-bottom: 1px solid var(--a-border-divider);
background: var(--a-surface-neutral-subtle-hover);
}

.navds-combobox__list-item__new-option--focus {
box-shadow: var(--a-shadow-focus) inset, var(--a-border-action) 0 0 0 5px inset;
border-radius: 3px;
}

/* Mobile */

@media (max-width: 479px) {
.navds-combobox__button-toggle-list {
right: 0.5rem;
}

/* add bigger click area for input */
.navds-combobox__input {
min-width: min-content;
padding: 0.75rem 0;
}

.navds-combobox__selected-options {
gap: var(--a-spacing-1);
}
}
1 change: 1 addition & 0 deletions @navikt/core/css/form/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
@import "text-field.css";
@import "textarea.css";
@import "search.css";
@import "combobox.css";
15 changes: 15 additions & 0 deletions @navikt/core/css/tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,21 @@
"--ac-search-button-focus-active-border": "--a-surface-default",
"--ac-search-error-border": "--a-border-danger"
},
"combobox": {
"--ac-combobox-clear-icon": "--a-text-subtle",
"--ac-combobox-clear-icon-hover": "--a-text-action-selected",
"--ac-combobox-clear-icon-active": "--a-text-action",
"--ac-combobox-list-bg": "--a-surface-default",
"--ac-combobox-list-text": "--a-text-default",
"--ac-combobox-list-border-color": "--a-border-divider",
"--ac-combobox-list-item-bg": "--a-surface-default",
"--ac-combobox-list-item-hover-bg": "--a-surface-hover",
"--ac-combobox-list-item-selected-bg": "--a-surface-default",
"--ac-combobox-list-item-selected-hover-bg": "--a-surface-action-subtle-hover",
"--ac-combobox-list-item-loading-bg": "--a-surface-default",
"--ac-combobox-list-item-hover-border-left": "--a-border-strong",
"--ac-combobox-list-item-selected-hover-border-left": "--a-border-focus"
},
"select": {
"--ac-select-bg": "--a-surface-default",
"--ac-select-text": "--a-text-default",
Expand Down
2 changes: 1 addition & 1 deletion @navikt/core/react/src/chips/Chips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const Chips: ChipsComponent = forwardRef<HTMLUListElement, ChipsProps>(
})}
>
{React.Children.map(children, (chip, index) => {
return <li key={index + (chip?.toString() ?? "")}>{chip}</li>;
return <li key={chip?.toString() || index}>{chip}</li>;
})}
</ul>
);
Expand Down
29 changes: 29 additions & 0 deletions @navikt/core/react/src/form/combobox/ClearButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";
import { XMarkIcon } from "@navikt/aksel-icons";

interface ClearButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
handleClear: (event: any) => void;
clearButtonLabel?: string;
}

export const ClearButton: React.FC<ClearButtonProps> = ({
handleClear,
clearButtonLabel,
...rest
}) => {
return (
<button
type="button"
onClick={handleClear}
className="navds-combobox__button-clear"
{...rest}
>
<span className="navds-sr-only">
{clearButtonLabel ? clearButtonLabel : "Tøm"}
</span>
<XMarkIcon aria-hidden />
</button>
);
};

export default ClearButton;
Loading

0 comments on commit e3027cc

Please sign in to comment.