@navikt/ds-react 0.14.9 → 0.14.10-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/cjs/form/search-field/SearchField.js +44 -23
  2. package/cjs/form/search-field/useSearchField.js +31 -0
  3. package/cjs/util/index.js +15 -1
  4. package/esm/form/search-field/SearchField.d.ts +25 -25
  5. package/esm/form/search-field/SearchField.js +46 -24
  6. package/esm/form/search-field/SearchField.js.map +1 -1
  7. package/esm/form/search-field/useSearchField.d.ts +10 -0
  8. package/esm/form/search-field/useSearchField.js +25 -0
  9. package/esm/form/search-field/useSearchField.js.map +1 -0
  10. package/esm/util/index.d.ts +5 -0
  11. package/esm/util/index.js +13 -0
  12. package/esm/util/index.js.map +1 -1
  13. package/package.json +2 -2
  14. package/src/form/search-field/SearchField.tsx +126 -71
  15. package/src/form/search-field/stories/search-field.stories.mdx +158 -89
  16. package/src/form/search-field/stories/search-field.stories.tsx +62 -154
  17. package/src/form/search-field/useSearchField.ts +31 -0
  18. package/src/util/index.ts +33 -0
  19. package/cjs/form/search-field/SearchFieldButton.js +0 -50
  20. package/cjs/form/search-field/SearchFieldClearButton.js +0 -50
  21. package/cjs/form/search-field/SearchFieldInput.js +0 -49
  22. package/esm/form/search-field/SearchFieldButton.d.ts +0 -17
  23. package/esm/form/search-field/SearchFieldButton.js +0 -27
  24. package/esm/form/search-field/SearchFieldButton.js.map +0 -1
  25. package/esm/form/search-field/SearchFieldClearButton.d.ts +0 -17
  26. package/esm/form/search-field/SearchFieldClearButton.js +0 -27
  27. package/esm/form/search-field/SearchFieldClearButton.js.map +0 -1
  28. package/esm/form/search-field/SearchFieldInput.d.ts +0 -6
  29. package/esm/form/search-field/SearchFieldInput.js +0 -26
  30. package/esm/form/search-field/SearchFieldInput.js.map +0 -1
  31. package/src/form/search-field/SearchFieldButton.tsx +0 -47
  32. package/src/form/search-field/SearchFieldClearButton.tsx +0 -49
  33. package/src/form/search-field/SearchFieldInput.tsx +0 -42
  34. package/src/form/search-field/stories/search-field-example.tsx +0 -25
@@ -1,35 +1,21 @@
1
+ import { Close, Search } from "@navikt/ds-icons";
1
2
  import cl from "classnames";
2
- import React, { forwardRef } from "react";
3
- import { BodyShort, Label, omit } from "../..";
4
- import ErrorMessage from "../ErrorMessage";
5
- import { FormFieldProps, useFormField } from "../useFormField";
6
- import SearchFieldButton, { SearchFieldButtonType } from "./SearchFieldButton";
7
- import SearchFieldClearButton, {
8
- SearchFieldClearButtonType,
9
- } from "./SearchFieldClearButton";
10
- import SearchFieldInput, { SearchFieldInputType } from "./SearchFieldInput";
11
-
12
- export interface SearchFieldContextProps {
13
- inputProps: {
14
- id: string;
15
- "aria-invalid": boolean;
16
- "aria-describedby"?: string;
17
- disabled?: boolean;
18
- };
19
- size?: "medium" | "small";
20
- }
21
-
22
- export const SearchFieldContext = React.createContext<SearchFieldContextProps | null>(
23
- null
24
- );
3
+ import React, {
4
+ forwardRef,
5
+ InputHTMLAttributes,
6
+ useCallback,
7
+ useEffect,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+ import mergeRefs from "react-merge-refs";
12
+ import { BodyShort, Label, omit, useEventListener } from "../..";
13
+ import { FormFieldProps } from "../useFormField";
14
+ import { useSearchField } from "./useSearchField";
25
15
 
26
16
  export interface SearchFieldProps
27
- extends FormFieldProps,
28
- React.HTMLAttributes<HTMLDivElement> {
29
- /**
30
- * SearchFieldInput & SearchFieldButton
31
- */
32
- children: React.ReactNode;
17
+ extends Omit<FormFieldProps, "error" | "errorId">,
18
+ Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange"> {
33
19
  /**
34
20
  * If enabled shows the label and description for screenreaders only
35
21
  */
@@ -38,50 +24,101 @@ export interface SearchFieldProps
38
24
  * SearchField label
39
25
  */
40
26
  label: React.ReactNode;
27
+ /**
28
+ * Inverts color theme
29
+ * @default false
30
+ */
31
+ inverted?: boolean;
32
+ /**
33
+ * Customize aria-label on clear button
34
+ * @default "Slett tekst i felt"
35
+ */
36
+ clearButtonLabel?: string;
37
+ /**
38
+ * Callback for when user manually clears input with button or Escape
39
+ */
40
+ onClear?: () => void;
41
+ /**
42
+ * Callback for value in input after change
43
+ */
44
+ onChange?: (value: string) => void;
45
+ /**
46
+ * Toggles display of "clear"-button when there is text in field
47
+ */
48
+ clearButton?: boolean;
41
49
  }
42
50
 
43
- interface SearchFieldComponent
44
- extends React.ForwardRefExoticComponent<
45
- SearchFieldProps & React.RefAttributes<HTMLDivElement>
46
- > {
47
- Button: SearchFieldButtonType;
48
- Clear: SearchFieldClearButtonType;
49
- Input: SearchFieldInputType;
50
- }
51
-
52
- const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
51
+ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
53
52
  (props, ref) => {
54
- const {
55
- inputProps,
56
- errorId,
57
- showErrorMsg,
58
- hasError,
59
- size,
60
- inputDescriptionId,
61
- } = useFormField(props, "searchfield");
53
+ const { inputProps, size, inputDescriptionId } = useSearchField(
54
+ props,
55
+ "searchfield"
56
+ );
62
57
 
63
58
  const {
64
59
  className,
65
60
  hideLabel,
66
- children,
67
61
  label,
68
62
  description,
69
- error,
63
+ value,
64
+ clearButtonLabel,
65
+ onClear,
66
+ inverted = false,
67
+ clearButton = true,
70
68
  ...rest
71
69
  } = props;
72
70
 
71
+ const [controlledValue, setControlledValue] = useState(value ?? "");
72
+ const searchRef = useRef<HTMLInputElement | null>(null);
73
+ const mergedRef = mergeRefs([searchRef, ref]);
74
+ const [wrapperRef, setWrapperRef] = useState<HTMLDivElement | null>(null);
75
+
76
+ const handleChange = useCallback(
77
+ (v: string) => {
78
+ if (searchRef.current && value === undefined) {
79
+ searchRef.current.value = v;
80
+ setControlledValue(v);
81
+ }
82
+ props?.onChange?.(v);
83
+ },
84
+ [props, value]
85
+ );
86
+
87
+ const handleClear = useCallback(() => {
88
+ onClear?.();
89
+ handleChange("");
90
+ searchRef.current && searchRef.current?.focus?.();
91
+ }, [handleChange, onClear]);
92
+
93
+ useEventListener(
94
+ "keydown",
95
+ useCallback(
96
+ (e) => {
97
+ if (e.key === "Escape") {
98
+ e.preventDefault();
99
+ handleClear();
100
+ }
101
+ },
102
+ [handleClear]
103
+ ),
104
+ wrapperRef
105
+ );
106
+
107
+ useEffect(() => {
108
+ value !== undefined && setControlledValue(value);
109
+ }, [value]);
110
+
73
111
  return (
74
112
  <div
75
- ref={ref}
76
- {...omit(rest, ["id", "error", "errorId", "size", "disabled"])}
113
+ ref={setWrapperRef}
77
114
  className={cl(
78
115
  className,
79
116
  "navds-form-field",
80
117
  `navds-form-field--${size ?? "medium"}`,
81
118
  "navds-search-field",
82
119
  {
83
- "navds-search-field--error": hasError,
84
120
  "navds-search-field--disabled": !!inputProps.disabled,
121
+ "navds-search-field--inverted": inverted,
85
122
  }
86
123
  )}
87
124
  >
@@ -90,7 +127,7 @@ const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
90
127
  size={size}
91
128
  as="label"
92
129
  className={cl("navds-text-field__label", {
93
- "sr-only": hideLabel,
130
+ "navds-sr-only": hideLabel,
94
131
  })}
95
132
  >
96
133
  {label}
@@ -99,7 +136,7 @@ const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
99
136
  <BodyShort
100
137
  as="div"
101
138
  className={cl("navds-text-field__description", {
102
- "sr-only": hideLabel,
139
+ "navds-sr-only": hideLabel,
103
140
  })}
104
141
  id={inputDescriptionId}
105
142
  size={size}
@@ -107,26 +144,44 @@ const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
107
144
  {description}
108
145
  </BodyShort>
109
146
  )}
110
- <div className="navds-search-field__input-wrapper">
111
- <SearchFieldContext.Provider
112
- value={{
113
- inputProps,
114
- size,
115
- }}
116
- >
117
- {children}
118
- </SearchFieldContext.Provider>
119
- </div>
120
- <div id={errorId} aria-relevant="additions removals" aria-live="polite">
121
- {showErrorMsg && <ErrorMessage size={size}>{error}</ErrorMessage>}
147
+ <div
148
+ data-value={!!controlledValue}
149
+ className="navds-search-field__input-wrapper"
150
+ >
151
+ <span className="navds-search-field__input-icon">
152
+ <Search aria-hidden />
153
+ </span>
154
+ <input
155
+ ref={mergedRef}
156
+ {...omit(rest, ["size"])}
157
+ {...inputProps}
158
+ {...(props.value !== undefined && { value: props.value })}
159
+ onChange={(e) => handleChange(e.target.value)}
160
+ type="search"
161
+ role="searchbox"
162
+ className={cl(
163
+ className,
164
+ "navds-search-field__input",
165
+ "navds-text-field__input",
166
+ "navds-body-short",
167
+ `navds-body-${size ?? "medium"}`
168
+ )}
169
+ />
170
+ {controlledValue && clearButton && (
171
+ <button
172
+ onClick={() => handleClear()}
173
+ className="navds-search-field__clear-button"
174
+ >
175
+ <span className="navds-sr-only">
176
+ {clearButtonLabel ? clearButtonLabel : "Slett tekst i felt"}
177
+ </span>
178
+ <Close aria-hidden />
179
+ </button>
180
+ )}
122
181
  </div>
123
182
  </div>
124
183
  );
125
184
  }
126
- ) as SearchFieldComponent;
127
-
128
- SearchField.Button = SearchFieldButton;
129
- SearchField.Clear = SearchFieldClearButton;
130
- SearchField.Input = SearchFieldInput;
185
+ );
131
186
 
132
187
  export default SearchField;
@@ -1,9 +1,8 @@
1
1
  import { Meta, Canvas } from "@storybook/addon-docs";
2
2
  import { SearchField } from "../index";
3
3
 
4
- import { Example } from "./search-field-example.tsx";
5
-
6
- import { Search } from "@navikt/ds-icons";
4
+ import { Table } from "@navikt/ds-react";
5
+ import { Header } from "@navikt/ds-react-internal";
7
6
 
8
7
  <Meta title="ds-react/form/search-field/intro" />
9
8
 
@@ -16,24 +15,47 @@ Komponenten er ikke testet eller gått gjennom et design-review, så anbefalses
16
15
  ## Bruk
17
16
 
18
17
  ```jsx
19
- <SearchField label="Mollit eiusmod" description="Ea cupidatat eu sunt commodo">
20
- <SearchField.Input />
21
- <SearchField.Button>
22
- <Search /> Søk
23
- </SearchField.Button>
24
- </SearchField>
18
+ <SearchField
19
+ label="Mollit eiusmod"
20
+ description="Ea cupidatat eu sunt commodo"
21
+ />
25
22
  ```
26
23
 
27
24
  <Canvas>
28
- <SearchField
29
- label="Mollit eiusmod"
30
- description="Ea cupidatat eu sunt commodo"
25
+ <div style={{ width: 300 }}>
26
+ <SearchField
27
+ label="Mollit eiusmod"
28
+ description="Ea cupidatat eu sunt commodo"
29
+ />
30
+ </div>
31
+ </Canvas>
32
+
33
+ ## Inverted
34
+
35
+ ```jsx
36
+ <SearchField
37
+ label="Mollit eiusmod"
38
+ description="Ea cupidatat eu sunt commodo"
39
+ hideLabel
40
+ inverted
41
+ />
42
+ ```
43
+
44
+ <Canvas>
45
+ <div
46
+ style={{
47
+ width: 300,
48
+ padding: "1rem",
49
+ background: "var(--navds-global-color-gray-900)",
50
+ }}
31
51
  >
32
- <SearchField.Input />
33
- <SearchField.Button>
34
- <Search /> Søk
35
- </SearchField.Button>
36
- </SearchField>
52
+ <SearchField
53
+ inverted
54
+ hideLabel
55
+ label="Mollit eiusmod"
56
+ description="Ea cupidatat eu sunt commodo"
57
+ />
58
+ </div>
37
59
  </Canvas>
38
60
 
39
61
  ## hideLabel
@@ -46,25 +68,41 @@ Vi har satt `label` som required siden man vil bruke den i sammenheng med denne
46
68
  label="Mollit eiusmod"
47
69
  description="Ea cupidatat eu sunt commodo"
48
70
  hideLabel
49
- >
50
- <SearchField.Input />
51
- <SearchField.Button>
52
- <Search /> Søk
53
- </SearchField.Button>
54
- </SearchField>
71
+ />
55
72
  ```
56
73
 
57
74
  <Canvas>
58
- <SearchField
59
- label="Mollit eiusmod"
60
- description="Ea cupidatat eu sunt commodo"
61
- hideLabel
62
- >
63
- <SearchField.Input />
64
- <SearchField.Button>
65
- <Search /> Søk
66
- </SearchField.Button>
67
- </SearchField>
75
+ <div style={{ width: 300 }}>
76
+ <SearchField
77
+ label="Mollit eiusmod"
78
+ description="Ea cupidatat eu sunt commodo"
79
+ hideLabel
80
+ />
81
+ </div>
82
+ </Canvas>
83
+
84
+ ## Uten "clear"-button
85
+
86
+ Fjerner "lukke"-knapp som dukker opp når feltet ikke er tomt.
87
+
88
+ ```jsx
89
+ <SearchField
90
+ label="Mollit eiusmod"
91
+ description="Ea cupidatat eu sunt commodo"
92
+ hideLabel
93
+ clearButton={false}
94
+ />
95
+ ```
96
+
97
+ <Canvas>
98
+ <div style={{ width: 300 }}>
99
+ <SearchField
100
+ label="Mollit eiusmod"
101
+ description="Ea cupidatat eu sunt commodo"
102
+ hideLabel
103
+ clearButton={false}
104
+ />
105
+ </div>
68
106
  </Canvas>
69
107
 
70
108
  ## Sizing
@@ -75,82 +113,113 @@ Vi har satt `label` som required siden man vil bruke den i sammenheng med denne
75
113
  description="Ea cupidatat eu sunt commodo"
76
114
  hideLabel
77
115
  size="small"
78
- >
79
- <SearchField.Input />
80
- <SearchField.Button>
81
- <Search /> Søk
82
- </SearchField.Button>
83
- </SearchField>
116
+ />
84
117
  ```
85
118
 
86
119
  <Canvas>
87
- <SearchField
88
- label="Mollit eiusmod"
89
- description="Ea cupidatat eu sunt commodo"
90
- hideLabel
91
- size="small"
92
- >
93
- <SearchField.Input />
94
- <SearchField.Button>
95
- <Search /> Søk
96
- </SearchField.Button>
97
- </SearchField>
120
+ <div style={{ width: 300 }}>
121
+ <SearchField
122
+ label="Mollit eiusmod"
123
+ description="Ea cupidatat eu sunt commodo"
124
+ hideLabel
125
+ size="small"
126
+ />
127
+ </div>
98
128
  </Canvas>
99
129
 
100
- ## Errors
130
+ ## Disabled
101
131
 
102
132
  ```jsx
103
133
  <SearchField
134
+ disabled
104
135
  label="Mollit eiusmod"
105
136
  description="Ea cupidatat eu sunt commodo"
106
137
  hideLabel
107
- error="Error message"
108
- >
109
- <SearchField.Input />
110
- <SearchField.Button>
111
- <Search /> Søk
112
- </SearchField.Button>
113
- </SearchField>
138
+
139
+ />
140
+ <SearchField
141
+ disabled
142
+ label="Mollit eiusmod"
143
+ description="Ea cupidatat eu sunt commodo"
144
+ hideLabel
145
+ size="small"
146
+ />
114
147
  ```
115
148
 
116
149
  <Canvas>
150
+ <div style={{ width: 300 }}>
151
+ <SearchField
152
+ disabled
153
+ label="Mollit eiusmod"
154
+ description="Ea cupidatat eu sunt commodo"
155
+ hideLabel
156
+ />
157
+ </div>
117
158
  <SearchField
159
+ disabled
118
160
  label="Mollit eiusmod"
119
161
  description="Ea cupidatat eu sunt commodo"
120
162
  hideLabel
121
- error="Error message"
122
- >
123
- <SearchField.Input />
124
- <SearchField.Button>
125
- <Search /> Søk
126
- </SearchField.Button>
127
- </SearchField>
163
+ size="small"
164
+ />
128
165
  </Canvas>
129
166
 
130
- ## ClearButton
131
-
132
- Man kan bruke `SearchField.Clear` for å håndtere clearing av value i `<input />`. For å bruke denne
133
- kreves det da at `SearchField.Input` er controlled slik at man styrer state selv slik som eksemplet under.
134
- Hvis man bare ønsker at den bare skal vises når bruker har begynt å skrive, anbefaler vi bare å rendre
135
- den når inputvalue ikke er empty.
136
-
137
- ```jsx
138
- <SearchField label="Mollit eiusmod" description="Ea cupidatat eu sunt commodo">
139
- <SearchField.Input value={value} onChange={(e) => setValue(e.target.value)} />
140
- {!!value && (
141
- <SearchField.Clear onClick={() => setValue("")}>
142
- <Close /> Tøm
143
- </SearchField.Clear>
144
- )}
145
- <SearchField.Button>
146
- <Search /> Søk
147
- </SearchField.Button>
148
- </SearchField>
149
- ```
167
+ ## Table
150
168
 
151
169
  <Canvas>
152
- <div>
153
- <Example />
154
- <Example size="small" />
155
- </div>
170
+ <Table>
171
+ <Table.Body>
172
+ <Table.Row>
173
+ <Table.HeaderCell scope="col">
174
+ <SearchField
175
+ label="Mollit eiusmod"
176
+ description="Ea cupidatat eu sunt commodo"
177
+ hideLabel
178
+ />
179
+ </Table.HeaderCell>
180
+ </Table.Row>
181
+ <Table.Row>
182
+ <Table.HeaderCell scope="col">Name</Table.HeaderCell>
183
+ <Table.HeaderCell scope="col">Age</Table.HeaderCell>
184
+ <Table.HeaderCell scope="col">Country</Table.HeaderCell>
185
+ <Table.HeaderCell scope="col">Points</Table.HeaderCell>
186
+ </Table.Row>
187
+ <Table.Row>
188
+ <Table.HeaderCell scope="row">
189
+ Donald SmithDonald Smith
190
+ </Table.HeaderCell>
191
+ <Table.DataCell>32</Table.DataCell>
192
+ <Table.DataCell>USA</Table.DataCell>
193
+ <Table.DataCell>38</Table.DataCell>
194
+ </Table.Row>
195
+ <Table.Row>
196
+ <Table.HeaderCell scope="row">Preben Aalborg</Table.HeaderCell>
197
+ <Table.DataCell>44</Table.DataCell>
198
+ <Table.DataCell>Denmark</Table.DataCell>
199
+ <Table.DataCell>11</Table.DataCell>
200
+ </Table.Row>
201
+ <Table.Row>
202
+ <Table.HeaderCell scope="row">Rudolph Bachenmeier</Table.HeaderCell>
203
+ <Table.DataCell>32</Table.DataCell>
204
+ <Table.DataCell>Germany</Table.DataCell>
205
+ <Table.DataCell>70</Table.DataCell>
206
+ </Table.Row>
207
+ </Table.Body>
208
+ </Table>
156
209
  </Canvas>
210
+
211
+ ## Header
212
+
213
+ <Header>
214
+ <Header.Title href="#">NAV Sykepenger</Header.Title>
215
+ <div style={{ margin: "auto 1rem auto auto" }}>
216
+ <SearchField
217
+ label="Mollit eiusmod"
218
+ description="Ea cupidatat eu sunt commodo"
219
+ hideLabel
220
+ size="small"
221
+ inverted
222
+ />
223
+ </div>
224
+ <Header.User name="Ola Normann" />
225
+ </Header>