Compare commits

...
Author SHA1 Message Date
Ken d9174b4352 Lerna versionbump [ci skip]
- @navikt/ds-css@0.12.11-next.0
 - @navikt/ds-react@0.14.10-next.0
 - @navikt/ds-react-internal@0.9.10-next.0
2022-01-15 22:32:02 +01:00
Ken eea7b7e2a5 v 2022-01-15 22:29:35 +01:00
Ken 83eabecc09 Merge branch 'master' into update/searchfield 2022-01-15 22:22:40 +01:00
Ken adb5db8dab toggle for clearbutton 2022-01-14 12:28:46 +01:00
Ken 6d2d9f7a21 fikset css margin-issue 2022-01-14 12:22:44 +01:00
Ken 965b6c9bc7 fikset events for searchbutton 2022-01-13 13:55:17 +01:00
Ken 674aabe50e hook og starte handling 2022-01-13 11:28:43 +01:00
Ken b9ba01a02d hover på button 2022-01-11 12:59:29 +01:00
Ken 98424123c2 la til inverted i stories 2022-01-11 12:11:32 +01:00
Ken bd6c524cad Merge branch 'master' into update/searchfield 2022-01-11 12:01:30 +01:00
Ken 750ed8376c storybook 2022-01-11 11:58:49 +01:00
Ken 815cd85da5 bruker nå spacing tokens 2022-01-11 11:32:20 +01:00
Ken 466d1b375a forenklet hover/focus 2022-01-11 11:28:12 +01:00
Ken 85aed1d104 tokens 2022-01-11 11:23:22 +01:00
Ken 479c46d3a7 fungerende inverted 2022-01-11 10:39:41 +01:00
Ken 28bea1e430 statet på inverted 2022-01-11 09:46:59 +01:00
Ken dc9a52c33c searchfield i header og tabell 2022-01-10 23:18:09 +01:00
Ken 755acf80f2 searchfield api + styling 2022-01-10 17:26:33 +01:00
Ken cf6bd1e5c3 søk ikon 2022-01-10 16:18:37 +01:00
14 changed files with 581 additions and 518 deletions

No files matched your search

+3 -1
View File
@@ -5,6 +5,8 @@
/* Applied when hideLabel is applied to form-element */
.navds-form-field > .sr-only + :not(:first-child):not(:empty),
.navds-form-field > .sr-only + input:not(:first-child) {
.navds-form-field > .sr-only + input:not(:first-child),
.navds-form-field > .navds-sr-only + :not(:first-child):not(:empty),
.navds-form-field > .navds-sr-only + input:not(:first-child) {
margin-top: 0;
}
+157 -29
View File
@@ -1,11 +1,48 @@
:root {
--navds-search-field-color-shadow-error: var(
--navds-semantic-color-interaction-danger
--navds-search-field-color-input-hover: var(
--navds-semantic-color-interaction-primary
);
--navds-search-field-color-border-error: var(
--navds-semantic-color-interaction-danger
--navds-search-field-color-button-text: var(--navds-semantic-color-text);
--navds-search-field-color-button-hover: var(
--navds-semantic-color-interaction-primary
);
--navds-search-field-color-clearbutton-border: var(
--navds-search-field-color-button-border-hover-focus: var(
--navds-semantic-color-interaction-primary
);
--navds-search-field-color-button-border-focus: var(
--navds-semantic-color-border
);
--navds-search-field-color-input-inverted: var(
--navds-semantic-color-component-background-light
);
--navds-search-field-color-input-inverted-border: var(
--navds-semantic-color-component-background-light
);
--navds-search-field-color-input-inverted-background: var(
--navds-semantic-color-component-background-inverted
);
--navds-search-field-color-input-inverted-background-active: var(
--navds-semantic-color-component-background-light
);
--navds-search-field-color-input-inverted-active: var(
--navds-semantic-color-text
);
--navds-search-field-color-input-inverted-border-active: var(
--navds-semantic-color-component-background-inverted
);
--navds-search-field-color-input-inverted-background-focus: var(
--navds-semantic-color-component-background-light
);
--navds-search-field-color-input-inverted-focus: var(
--navds-semantic-color-text
);
--navds-search-field-color-input-inverted-border-focus: var(
--navds-semantic-color-component-background-inverted
);
--navds-search-field-color-input-inverted-hover-focus: var(
--navds-semantic-color-focus-inverted
);
--navds-search-field-color-button-inverted-focus: var(
--navds-semantic-color-border
);
}
@@ -14,46 +51,137 @@
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.navds-search-field__input-icon {
position: absolute;
left: 0;
padding: 0 var(--navds-spacing-4);
align-items: center;
display: flex;
pointer-events: none;
}
.navds-search-field__input {
padding-left: var(--navds-spacing-11);
padding-right: var(--navds-spacing-12);
border-width: 1px;
}
.navds-form-field--small .navds-search-field__input {
padding: 0.15rem;
min-height: 32px;
padding: 0.15rem var(--navds-spacing-8) 0.15rem var(--navds-spacing-7);
}
.navds-search-field__input-wrapper > *:not(:first-child):not(:last-child) {
border-radius: 0;
.navds-form-field--small .navds-search-field__input-icon {
padding: 0 var(--navds-spacing-2);
}
.navds-search-field__input-wrapper > :first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.navds-search-field__input:hover {
border-color: var(--navds-search-field-color-input-hover);
}
.navds-search-field__input-wrapper > :last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
/* Clear Button */
.navds-search-field__input-wrapper > :last-child:first-child {
border-radius: 4px;
}
.navds-search-field__input-wrapper :focus {
z-index: var(--navds-z-index-focus);
}
.navds-search-field__button,
.navds-search-field__clear-button {
flex-shrink: 0;
position: absolute;
right: 0;
color: var(--navds-search-field-color-button-text);
appearance: none;
background: none;
height: 48px;
width: 48px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
font-size: 1.5rem;
}
.navds-search-field--error
.navds-search-field__input:not(:hover):not(:focus):not(:disabled) {
box-shadow: 0 0 0 1px var(--navds-search-field-color-shadow-error) inset;
border-color: var(--navds-search-field-color-border-error);
.navds-form-field--small .navds-search-field__clear-button {
height: 32px;
width: 32px;
font-size: 1rem;
}
.navds-search-field__clear-button:focus {
box-shadow: var(--navds-shadow-focus);
border-color: var(--navds-search-field-color-button-border-focus);
outline: none;
}
.navds-search-field__clear-button:hover {
color: var(--navds-search-field-color-button-hover);
}
.navds-search-field__clear-button:hover:focus {
border-color: var(--navds-search-field-color-button-border-hover-focus);
}
/* Inverted */
.navds-search-field--inverted
> .navds-search-field__input-wrapper
> .navds-search-field__input {
background-color: var(--navds-search-field-color-inverted-background);
color: var(--navds-search-field-color-input-inverted);
border-color: var(--navds-search-field-color-input-inverted-border);
}
.navds-search-field--inverted
> .navds-search-field__input-wrapper[data-value="true"]
> .navds-search-field__input {
background-color: var(
--navds-search-field-color-input-inverted-background-active
);
color: var(--navds-search-field-color-input-inverted-active);
border-color: var(--navds-search-field-color-input-inverted-border-active);
}
.navds-search-field--inverted
> .navds-search-field__input-wrapper[data-value="true"] {
color: var(--navds-search-field-color-input-inverted-active);
}
.navds-search-field--inverted > .navds-search-field__input-wrapper {
color: var(--navds-search-field-color-input-inverted);
}
.navds-search-field--inverted
> .navds-search-field__input-wrapper
> .navds-search-field__input:focus {
background-color: var(
--navds-search-field-color-input-inverted-background-focus
);
color: var(--navds-search-field-color-input-inverted-focus);
border-color: var(--navds-search-field-color-input-inverted-border-focus);
box-shadow: 0 0 0 3px var(--navds-semantic-color-focus-inverted);
}
.navds-search-field--inverted
> .navds-search-field__input-wrapper:focus-within {
color: var(--navds-search-field-color-input-inverted-focus);
}
/* Inveted clear button */
.navds-search-field--inverted
> .navds-search-field__input-wrapper
> .navds-search-field__clear-button:focus {
box-shadow: 0 0 0 3px var(--navds-semantic-color-focus-inverted);
border-color: var(--navds-search-field-color-button-inverted-focus);
outline: none;
}
.navds-search-field--inverted
> .navds-search-field__input-wrapper
> .navds-search-field__clear-button:hover:focus {
border-color: var(--navds-search-field-color-input-inverted-hover-focus);
}
/* disabled */
.navds-search-field--disabled {
opacity: 0.3;
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "0.12.9",
"version": "0.12.11-next.0",
"private": false,
"description": "Css for NAV Designsystem components",
"author": "NAV Designsystem team",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "0.14.8",
"version": "0.14.10-next.0",
"private": false,
"description": "NAV designsystem react components",
"author": "NAV Designsystem team",
@@ -1,35 +1,21 @@
import { Close, Search } from "@navikt/ds-icons";
import cl from "classnames";
import React, { forwardRef } from "react";
import { BodyShort, Label, omit } from "../..";
import ErrorMessage from "../ErrorMessage";
import { FormFieldProps, useFormField } from "../useFormField";
import SearchFieldButton, { SearchFieldButtonType } from "./SearchFieldButton";
import SearchFieldClearButton, {
SearchFieldClearButtonType,
} from "./SearchFieldClearButton";
import SearchFieldInput, { SearchFieldInputType } from "./SearchFieldInput";
export interface SearchFieldContextProps {
inputProps: {
id: string;
"aria-invalid": boolean;
"aria-describedby"?: string;
disabled?: boolean;
};
size?: "medium" | "small";
}
export const SearchFieldContext = React.createContext<SearchFieldContextProps | null>(
null
);
import React, {
forwardRef,
InputHTMLAttributes,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import mergeRefs from "react-merge-refs";
import { BodyShort, Label, omit, useEventListener } from "../..";
import { FormFieldProps } from "../useFormField";
import { useSearchField } from "./useSearchField";
export interface SearchFieldProps
extends FormFieldProps,
React.HTMLAttributes<HTMLDivElement> {
/**
* SearchFieldInput & SearchFieldButton
*/
children: React.ReactNode;
extends Omit<FormFieldProps, "error" | "errorId">,
Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange"> {
/**
* If enabled shows the label and description for screenreaders only
*/
@@ -38,50 +24,101 @@ export interface SearchFieldProps
* SearchField label
*/
label: React.ReactNode;
/**
* Inverts color theme
* @default false
*/
inverted?: boolean;
/**
* Customize aria-label on clear button
* @default "Slett tekst i felt"
*/
clearButtonLabel?: string;
/**
* Callback for when user manually clears input with button or Escape
*/
onClear?: () => void;
/**
* Callback for value in input after change
*/
onChange?: (value: string) => void;
/**
* Toggles display of "clear"-button when there is text in field
*/
clearButton?: boolean;
}
interface SearchFieldComponent
extends React.ForwardRefExoticComponent<
SearchFieldProps & React.RefAttributes<HTMLDivElement>
> {
Button: SearchFieldButtonType;
Clear: SearchFieldClearButtonType;
Input: SearchFieldInputType;
}
const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
(props, ref) => {
const {
inputProps,
errorId,
showErrorMsg,
hasError,
size,
inputDescriptionId,
} = useFormField(props, "searchfield");
const { inputProps, size, inputDescriptionId } = useSearchField(
props,
"searchfield"
);
const {
className,
hideLabel,
children,
label,
description,
error,
value,
clearButtonLabel,
onClear,
inverted = false,
clearButton = true,
...rest
} = props;
const [controlledValue, setControlledValue] = useState(value ?? "");
const searchRef = useRef<HTMLInputElement | null>(null);
const mergedRef = mergeRefs([searchRef, ref]);
const [wrapperRef, setWrapperRef] = useState<HTMLDivElement | null>(null);
const handleChange = useCallback(
(v: string) => {
if (searchRef.current && value === undefined) {
searchRef.current.value = v;
setControlledValue(v);
}
props?.onChange?.(v);
},
[props, value]
);
const handleClear = useCallback(() => {
onClear?.();
handleChange("");
searchRef.current && searchRef.current?.focus?.();
}, [handleChange, onClear]);
useEventListener(
"keydown",
useCallback(
(e) => {
if (e.key === "Escape") {
e.preventDefault();
handleClear();
}
},
[handleClear]
),
wrapperRef
);
useEffect(() => {
value !== undefined && setControlledValue(value);
}, [value]);
return (
<div
ref={ref}
{...omit(rest, ["id", "error", "errorId", "size", "disabled"])}
ref={setWrapperRef}
className={cl(
className,
"navds-form-field",
`navds-form-field--${size ?? "medium"}`,
"navds-search-field",
{
"navds-search-field--error": hasError,
"navds-search-field--disabled": !!inputProps.disabled,
"navds-search-field--inverted": inverted,
}
)}
>
@@ -90,7 +127,7 @@ const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
size={size}
as="label"
className={cl("navds-text-field__label", {
"sr-only": hideLabel,
"navds-sr-only": hideLabel,
})}
>
{label}
@@ -99,7 +136,7 @@ const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
<BodyShort
as="div"
className={cl("navds-text-field__description", {
"sr-only": hideLabel,
"navds-sr-only": hideLabel,
})}
id={inputDescriptionId}
size={size}
@@ -107,26 +144,44 @@ const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
{description}
</BodyShort>
)}
<div className="navds-search-field__input-wrapper">
<SearchFieldContext.Provider
value={{
inputProps,
size,
}}
>
{children}
</SearchFieldContext.Provider>
</div>
<div id={errorId} aria-relevant="additions removals" aria-live="polite">
{showErrorMsg && <ErrorMessage size={size}>{error}</ErrorMessage>}
<div
data-value={!!controlledValue}
className="navds-search-field__input-wrapper"
>
<span className="navds-search-field__input-icon">
<Search aria-hidden />
</span>
<input
ref={mergedRef}
{...omit(rest, ["size"])}
{...inputProps}
{...(props.value !== undefined && { value: props.value })}
onChange={(e) => handleChange(e.target.value)}
type="search"
role="searchbox"
className={cl(
className,
"navds-search-field__input",
"navds-text-field__input",
"navds-body-short",
`navds-body-${size ?? "medium"}`
)}
/>
{controlledValue && clearButton && (
<button
onClick={() => handleClear()}
className="navds-search-field__clear-button"
>
<span className="navds-sr-only">
{clearButtonLabel ? clearButtonLabel : "Slett tekst i felt"}
</span>
<Close aria-hidden />
</button>
)}
</div>
</div>
);
}
) as SearchFieldComponent;
SearchField.Button = SearchFieldButton;
SearchField.Clear = SearchFieldClearButton;
SearchField.Input = SearchFieldInput;
);
export default SearchField;
@@ -1,47 +0,0 @@
import React, { forwardRef, useContext } from "react";
import cl from "classnames";
import { SearchFieldContext } from "./SearchField";
import { Button, ButtonProps } from "../..";
export interface SearchFieldButtonProps extends Omit<ButtonProps, "size"> {
/**
* Button text
* @example <Search /> <span>Søk</span>
*/
children: React.ReactNode;
/**
* Changes design and interactions
* @default "primary"
*/
variant?: "primary" | "secondary";
}
export type SearchFieldButtonType = React.ForwardRefExoticComponent<
SearchFieldButtonProps & React.RefAttributes<HTMLButtonElement>
>;
const SearchFieldButton: SearchFieldButtonType = forwardRef(
({ className, variant = "primary", disabled, ...rest }, ref) => {
const searchField = useContext(SearchFieldContext);
if (searchField === null) {
console.warn("SearchFieldButton has to be wrapped in <SearchField />");
return null;
}
const { size, inputProps } = searchField;
return (
<Button
ref={ref}
className={cl(className, "navds-search-field__button")}
{...rest}
size={size}
variant={variant}
disabled={disabled ?? inputProps?.disabled}
/>
);
}
);
export default SearchFieldButton;
@@ -1,49 +0,0 @@
import React, { forwardRef, useContext } from "react";
import cl from "classnames";
import { SearchFieldContext } from "./SearchField";
import { Button, ButtonProps } from "../..";
export interface SearchFieldClearButtonProps extends Omit<ButtonProps, "size"> {
/**
* Button text
* @example <Close /> <span>Søk</span>
*/
children: React.ReactNode;
/**
* Changes design and interactions
* @default "secondary"
*/
variant?: "primary" | "secondary";
}
export type SearchFieldClearButtonType = React.ForwardRefExoticComponent<
SearchFieldClearButtonProps & React.RefAttributes<HTMLButtonElement>
>;
const SearchFieldClearButton: SearchFieldClearButtonType = forwardRef(
({ className, variant = "secondary", disabled, ...rest }, ref) => {
const searchField = useContext(SearchFieldContext);
if (searchField === null) {
console.warn(
"SearchFieldClearButton has to be wrapped in <SearchField />"
);
return null;
}
const { size, inputProps } = searchField;
return (
<Button
{...rest}
ref={ref}
className={cl(className, "navds-search-field__clear-button")}
size={size}
variant={variant}
disabled={disabled ?? inputProps?.disabled}
/>
);
}
);
export default SearchFieldClearButton;
@@ -1,42 +0,0 @@
import React, { forwardRef, InputHTMLAttributes, useContext } from "react";
import cl from "classnames";
import { SearchFieldContext } from "./SearchField";
export interface SearchFieldInputProps
extends InputHTMLAttributes<HTMLInputElement> {}
export type SearchFieldInputType = React.ForwardRefExoticComponent<
SearchFieldInputProps & React.RefAttributes<HTMLInputElement>
>;
const SearchFieldInput: SearchFieldInputType = forwardRef(
({ className, ...rest }, ref) => {
const searchField = useContext(SearchFieldContext);
if (searchField === null) {
console.warn("SearchFieldInput has to be wrapped in <SearchField />");
return null;
}
const { size, inputProps } = searchField;
return (
<input
{...rest}
{...inputProps}
type="search"
role="searchbox"
ref={ref}
className={cl(
className,
"navds-search-field__input",
"navds-text-field__input",
"navds-body-short",
`navds-body-${size ?? "medium"}`
)}
/>
);
}
);
export default SearchFieldInput;
@@ -1,25 +0,0 @@
import React, { useState } from "react";
import { Close, Search } from "@navikt/ds-icons";
import { SearchField } from "../index";
export const Example = ({ size = "medium" }: { size: "medium" | "small" }) => {
const [value, setValue] = useState("");
return (
<SearchField size={size} label="Skriv i søkefeltet for å vise clearbutton">
<SearchField.Input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
{!!value && (
<SearchField.Clear onClick={() => setValue("")}>
<Close />
Tøm
</SearchField.Clear>
)}
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
);
};
@@ -1,9 +1,8 @@
import { Meta, Canvas } from "@storybook/addon-docs";
import { SearchField } from "../index";
import { Example } from "./search-field-example.tsx";
import { Search } from "@navikt/ds-icons";
import { Table } from "@navikt/ds-react";
import { Header } from "@navikt/ds-react-internal";
<Meta title="ds-react/form/search-field/intro" />
@@ -16,24 +15,47 @@ Komponenten er ikke testet eller gått gjennom et design-review, så anbefalses
## Bruk
```jsx
<SearchField label="Mollit eiusmod" description="Ea cupidatat eu sunt commodo">
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
/>
```
<Canvas>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
<div style={{ width: 300 }}>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
/>
</div>
</Canvas>
## Inverted
```jsx
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
inverted
/>
```
<Canvas>
<div
style={{
width: 300,
padding: "1rem",
background: "var(--navds-global-color-gray-900)",
}}
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
inverted
hideLabel
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
/>
</div>
</Canvas>
## hideLabel
@@ -46,25 +68,41 @@ Vi har satt `label` som required siden man vil bruke den i sammenheng med denne
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
/>
```
<Canvas>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<div style={{ width: 300 }}>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
/>
</div>
</Canvas>
## Uten "clear"-button
Fjerner "lukke"-knapp som dukker opp når feltet ikke er tomt.
```jsx
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
clearButton={false}
/>
```
<Canvas>
<div style={{ width: 300 }}>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
clearButton={false}
/>
</div>
</Canvas>
## Sizing
@@ -75,82 +113,113 @@ Vi har satt `label` som required siden man vil bruke den i sammenheng med denne
description="Ea cupidatat eu sunt commodo"
hideLabel
size="small"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
/>
```
<Canvas>
<div style={{ width: 300 }}>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
size="small"
/>
</div>
</Canvas>
## Disabled
```jsx
<SearchField
disabled
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
/>
<SearchField
disabled
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
size="small"
/>
```
<Canvas>
<div style={{ width: 300 }}>
<SearchField
disabled
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
/>
</div>
<SearchField
disabled
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
size="small"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
/>
</Canvas>
## Errors
```jsx
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
error="Error message"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
```
## Table
<Canvas>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
error="Error message"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<Table>
<Table.Body>
<Table.Row>
<Table.HeaderCell scope="col">
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
/>
</Table.HeaderCell>
</Table.Row>
<Table.Row>
<Table.HeaderCell scope="col">Name</Table.HeaderCell>
<Table.HeaderCell scope="col">Age</Table.HeaderCell>
<Table.HeaderCell scope="col">Country</Table.HeaderCell>
<Table.HeaderCell scope="col">Points</Table.HeaderCell>
</Table.Row>
<Table.Row>
<Table.HeaderCell scope="row">
Donald SmithDonald Smith
</Table.HeaderCell>
<Table.DataCell>32</Table.DataCell>
<Table.DataCell>USA</Table.DataCell>
<Table.DataCell>38</Table.DataCell>
</Table.Row>
<Table.Row>
<Table.HeaderCell scope="row">Preben Aalborg</Table.HeaderCell>
<Table.DataCell>44</Table.DataCell>
<Table.DataCell>Denmark</Table.DataCell>
<Table.DataCell>11</Table.DataCell>
</Table.Row>
<Table.Row>
<Table.HeaderCell scope="row">Rudolph Bachenmeier</Table.HeaderCell>
<Table.DataCell>32</Table.DataCell>
<Table.DataCell>Germany</Table.DataCell>
<Table.DataCell>70</Table.DataCell>
</Table.Row>
</Table.Body>
</Table>
</Canvas>
## ClearButton
## Header
Man kan bruke `SearchField.Clear` for å håndtere clearing av value i `<input />`. For å bruke denne
kreves det da at `SearchField.Input` er controlled slik at man styrer state selv slik som eksemplet under.
Hvis man bare ønsker at den bare skal vises når bruker har begynt å skrive, anbefaler vi bare å rendre
den når inputvalue ikke er empty.
```jsx
<SearchField label="Mollit eiusmod" description="Ea cupidatat eu sunt commodo">
<SearchField.Input value={value} onChange={(e) => setValue(e.target.value)} />
{!!value && (
<SearchField.Clear onClick={() => setValue("")}>
<Close /> Tøm
</SearchField.Clear>
)}
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
```
<Canvas>
<div>
<Example />
<Example size="small" />
<Header>
<Header.Title href="#">NAV Sykepenger</Header.Title>
<div style={{ margin: "auto 1rem auto auto" }}>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
size="small"
inverted
/>
</div>
</Canvas>
<Header.User name="Ola Normann" />
</Header>
@@ -1,174 +1,77 @@
import React, { useState } from "react";
import { Meta } from "@storybook/react/types-6-0";
import { Close, Search } from "@navikt/ds-icons";
import { SearchField } from "../index";
import React, { useState } from "react";
import { Fieldset } from "../..";
import { SearchField } from "../index";
export default {
title: "ds-react/form/search-field",
component: SearchField,
} as Meta;
export const All = () => {
const [show, setShow] = useState(false);
const [value, setValue] = useState("");
return (
<>
<h1>SearchField</h1>
<SearchField
label="Mollit eiusmod"
description={<div>Ea cupidatat eu sunt commodo</div>}
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Input />
</SearchField>
<h1>SearchField w clearsearch</h1>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
>
<SearchField.Input />
<SearchField.Clear>
<Close />
</SearchField.Clear>
<SearchField.Button onClick={() => setShow(!show)}>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Clear>
<Close /> Tøm
</SearchField.Clear>
<SearchField.Input />
</SearchField>
<div>
<SearchField label="Mollit eiusmod" />
<SearchField
label="Mollit eiusmod"
description={<div>Ea cupidatat eu sunt commodo</div>}
/>
</div>
<h2>Hidelabel</h2>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
/>
<h2>Inverted</h2>
<div
style={{
width: 300,
padding: "1rem",
background: "var(--navds-global-color-gray-900)",
}}
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Input />
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
inverted
/>
<br />
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
inverted
value="Søketekst"
/>
</div>
<h2>SearchField small</h2>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
size="small"
>
<SearchField.Button>
<Search />
<span className="sr-only">Søk</span>
</SearchField.Button>
<SearchField.Input />
</SearchField>
/>
<br />
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
size="small"
>
<SearchField.Input />
<SearchField.Button>
<Search />
<span className="sr-only">Søk</span>
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
size="small"
>
<SearchField.Button>
<Search />
<span className="sr-only">Søk</span>
</SearchField.Button>
<SearchField.Clear>
<Close /> Tøm
</SearchField.Clear>
<SearchField.Input />
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
size="small"
>
<SearchField.Input />
<SearchField.Clear>
<Close /> Tøm
</SearchField.Clear>
<SearchField.Button>
<Search />
<span className="sr-only">Søk</span>
</SearchField.Button>
</SearchField>
<h2>SearchField w error</h2>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
error="Errormsg"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
error="Errormsg"
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Input />
</SearchField>
hideLabel
/>
<h2>SearchField in Fieldset</h2>
<Fieldset legend="Filter" error="Fieldset-error-msg">
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
error="Errormsg"
hideLabel
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
hideLabel
>
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
<SearchField.Input />
</SearchField>
/>
</Fieldset>
<h2>Disabled </h2>
@@ -176,24 +79,29 @@ export const All = () => {
disabled
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
<Fieldset legend="Filter" disabled>
<SearchField
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
error="Errormsg"
>
<SearchField.Input />
<SearchField.Button>
<Search /> Søk
</SearchField.Button>
</SearchField>
</Fieldset>
/>
<h2>Controlled state </h2>
<SearchField
value={value}
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
onChange={(e) => setValue(e)}
onClear={() => setValue("")}
/>
<h3>Controlled state no clear</h3>
<SearchField
value={value}
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
onChange={(e) => setValue(e)}
/>
<h2>No clear button</h2>
<SearchField
hideLabel
label="Mollit eiusmod"
description="Ea cupidatat eu sunt commodo"
clearButton={false}
/>
</>
);
};
@@ -0,0 +1,31 @@
import cl from "classnames";
import { useContext } from "react";
import { useId } from "../../index";
import { FieldsetContext } from "../index";
import { FormFieldProps } from "../useFormField";
export const useSearchField = (props: FormFieldProps, prefix: string) => {
const { size } = props;
const fieldset = useContext(FieldsetContext);
const genId = useId();
const id = props.id ?? `${prefix}-${genId}`;
const inputDescriptionId = `${prefix}-description-${genId}`;
const disabled = fieldset?.disabled || props.disabled;
return {
inputDescriptionId,
size: size ?? fieldset?.size ?? "medium",
inputProps: {
id,
"aria-describedby":
cl(props["aria-describedby"], {
[inputDescriptionId]: !!props?.description,
}) || undefined,
disabled,
},
};
};
+33
View File
@@ -1,3 +1,5 @@
import { useEffect } from "react";
export * from "./OverridableComponent";
export * from "./useId";
@@ -11,3 +13,34 @@ export const omit = (obj: object, props: string[]) =>
}),
{}
);
export interface ListenerT {
addEventListener(
name: string,
handler: (event?: any) => void,
...args: any[]
);
removeEventListener(
name: string,
handler: (event?: any) => void,
...args: any[]
);
}
/* https://github.com/streamich/react-use/blob/master/src/useEvent.ts */
export const useEventListener = <T extends ListenerT>(
name: Parameters<ListenerT["addEventListener"]>[0],
handler: Parameters<ListenerT["addEventListener"]>[1],
target: null | T | Window = window
): void => {
useEffect(() => {
if (!target) {
return;
}
target?.addEventListener(name, handler);
return () => {
target?.addEventListener(name, handler);
};
}, [name, handler, target]);
};
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react-internal",
"version": "0.9.8",
"version": "0.9.10-next.0",
"private": false,
"description": "NAV internal designsystem react components",
"author": "NAV Designsystem team",
@@ -34,7 +34,7 @@
},
"dependencies": {
"@navikt/ds-icons": "^0.7.2",
"@navikt/ds-react": "^0.14.8",
"@navikt/ds-react": "^0.14.10-next.0",
"@popperjs/core": "^2.10.1",
"classnames": "^2.3.1",
"copy-to-clipboard": "^3.3.1",