@navikt/ds-react 5.3.0 → 5.3.2

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 (50) hide show
  1. package/_docs.json +25 -88
  2. package/cjs/date/DateInput.js +1 -1
  3. package/cjs/date/monthpicker/MonthButton.js +5 -5
  4. package/cjs/dropdown/Menu/List/index.js +1 -0
  5. package/cjs/guide-panel/GuidePanel.js +7 -5
  6. package/cjs/guide-panel/Illustration.js +12 -16
  7. package/cjs/list/ListItem.js +1 -0
  8. package/cjs/skeleton/Skeleton.js +2 -2
  9. package/esm/date/DateInput.js +1 -1
  10. package/esm/date/DateInput.js.map +1 -1
  11. package/esm/date/monthpicker/MonthButton.js +5 -5
  12. package/esm/date/monthpicker/MonthButton.js.map +1 -1
  13. package/esm/dropdown/Menu/List/index.js +1 -0
  14. package/esm/dropdown/Menu/List/index.js.map +1 -1
  15. package/esm/guide-panel/GuidePanel.d.ts +3 -3
  16. package/esm/guide-panel/GuidePanel.js +7 -5
  17. package/esm/guide-panel/GuidePanel.js.map +1 -1
  18. package/esm/guide-panel/Illustration.js +12 -16
  19. package/esm/guide-panel/Illustration.js.map +1 -1
  20. package/esm/guide-panel/index.d.ts +0 -1
  21. package/esm/guide-panel/index.js.map +1 -1
  22. package/esm/layout/stack/Stack.js.map +1 -1
  23. package/esm/list/ListItem.js +1 -0
  24. package/esm/list/ListItem.js.map +1 -1
  25. package/esm/skeleton/Skeleton.d.ts +5 -0
  26. package/esm/skeleton/Skeleton.js +2 -2
  27. package/esm/skeleton/Skeleton.js.map +1 -1
  28. package/package.json +3 -3
  29. package/src/alert/alert.stories.tsx +11 -24
  30. package/src/date/DateInput.tsx +1 -1
  31. package/src/date/datepicker/datepicker.stories.tsx +23 -19
  32. package/src/date/monthpicker/MonthButton.tsx +5 -5
  33. package/src/date/monthpicker/monthpicker.stories.tsx +30 -32
  34. package/src/dropdown/Menu/List/index.tsx +1 -1
  35. package/src/form/combobox/combobox.stories.tsx +142 -188
  36. package/src/guide-panel/GuidePanel.tsx +11 -10
  37. package/src/guide-panel/Illustration.tsx +23 -47
  38. package/src/guide-panel/guidepanel.stories.tsx +37 -12
  39. package/src/guide-panel/index.ts +0 -1
  40. package/src/layout/stack/Stack.tsx +2 -2
  41. package/src/list/ListItem.tsx +1 -0
  42. package/src/react-css.d.ts +9 -0
  43. package/src/skeleton/Skeleton.tsx +17 -3
  44. package/src/skeleton/skeleton.stories.tsx +13 -2
  45. package/src/table/stories/table-async.stories.tsx +19 -7
  46. package/cjs/guide-panel/Guide.js +0 -48
  47. package/esm/guide-panel/Guide.d.ts +0 -14
  48. package/esm/guide-panel/Guide.js +0 -21
  49. package/esm/guide-panel/Guide.js.map +0 -1
  50. package/src/guide-panel/Guide.tsx +0 -34
@@ -1,26 +1,18 @@
1
1
  /* eslint-disable react-hooks/rules-of-hooks */
2
- import { Meta } from "@storybook/react";
2
+ import { Meta, StoryFn, StoryObj } from "@storybook/react";
3
3
  import React, { useState, useId, useMemo } from "react";
4
4
  import { userEvent, within } from "@storybook/testing-library";
5
- import { Chips, UNSAFE_Combobox, TextField } from "../../index";
5
+ import { Chips, UNSAFE_Combobox, ComboboxProps, TextField } from "../../index";
6
6
  import { expect, jest } from "@storybook/jest";
7
7
 
8
8
  export default {
9
9
  title: "ds-react/Combobox",
10
10
  component: UNSAFE_Combobox,
11
- argTypes: {
12
- isListOpen: {
13
- control: {
14
- type: "boolean",
15
- },
16
- },
17
- isLoading: {
18
- control: {
19
- type: "boolean",
20
- },
21
- },
22
- },
23
- } as Meta;
11
+ decorators: [(story) => <div style={{ width: "300px" }}>{story()}</div>],
12
+ } satisfies Meta<typeof UNSAFE_Combobox>;
13
+
14
+ type StoryObject = StoryObj<typeof UNSAFE_Combobox>;
15
+ type StoryFunction = StoryFn<typeof UNSAFE_Combobox>;
24
16
 
25
17
  const options = [
26
18
  "banana",
@@ -38,55 +30,39 @@ const options = [
38
30
  "grape fruit",
39
31
  ];
40
32
 
41
- const initialSelectedOptions = ["passion fruit", "grape fruit"];
42
-
43
- const DemoContainer = ({
44
- dataTheme,
45
- children,
46
- }: {
47
- children: any;
48
- dataTheme: "dark" | "light";
49
- }) => (
50
- <div data-theme={dataTheme} style={{ width: "300px" }}>
51
- {children}
52
- </div>
53
- );
54
-
55
- export const Default = (props) => {
33
+ export const Default: StoryFunction = (props) => {
56
34
  const id = useId();
57
- return (
58
- <DemoContainer dataTheme={props.darkMode}>
59
- <UNSAFE_Combobox
60
- options={props.options}
61
- label="Hva er dine favorittfrukter?"
62
- /* everything under here is optional? */
63
- shouldAutocomplete={props.shouldAutoComplete}
64
- size="medium"
65
- id={id}
66
- />
67
- </DemoContainer>
68
- );
35
+ return <UNSAFE_Combobox {...props} id={id} />;
69
36
  };
70
-
71
37
  Default.args = {
72
38
  options,
73
- shouldAutoComplete: true,
39
+ label: "Hva er dine favorittfrukter?",
40
+ shouldAutocomplete: true,
41
+ isLoading: false,
42
+ };
43
+ Default.argTypes = {
44
+ isListOpen: {
45
+ control: { type: "boolean" },
46
+ },
47
+ size: {
48
+ options: ["medium", "small"],
49
+ defaultValue: "medium",
50
+ control: { type: "radio" },
51
+ },
74
52
  };
75
53
 
76
- export function MultiSelect(props) {
54
+ export const MultiSelect: StoryFunction = (props) => {
77
55
  const id = useId();
78
56
  return (
79
- <DemoContainer dataTheme={props.darkMode}>
80
- <UNSAFE_Combobox
81
- id={id}
82
- label="Komboboks - velg flere"
83
- options={props.options}
84
- isMultiSelect={props.isMultiSelect}
85
- size={props.size}
86
- />
87
- </DemoContainer>
57
+ <UNSAFE_Combobox
58
+ id={id}
59
+ label="Komboboks - velg flere"
60
+ options={props.options}
61
+ isMultiSelect={props.isMultiSelect}
62
+ size={props.size}
63
+ />
88
64
  );
89
- }
65
+ };
90
66
 
91
67
  MultiSelect.args = {
92
68
  options,
@@ -94,41 +70,37 @@ MultiSelect.args = {
94
70
  size: "medium",
95
71
  };
96
72
 
97
- export function WithAddNewOptions(props) {
73
+ export const WithAddNewOptions: StoryFunction = (props) => {
98
74
  const id = useId();
99
75
  return (
100
- <DemoContainer dataTheme={props.darkMode}>
101
- <UNSAFE_Combobox
102
- id={id}
103
- label="Komboboks med mulighet for å legge til nye verdier"
104
- options={props.options}
105
- allowNewValues={props.allowNewValues}
106
- shouldAutocomplete={props.shouldAutoComplete}
107
- />
108
- </DemoContainer>
76
+ <UNSAFE_Combobox
77
+ id={id}
78
+ label="Komboboks med mulighet for å legge til nye verdier"
79
+ options={props.options}
80
+ allowNewValues={props.allowNewValues}
81
+ shouldAutocomplete={props.shouldAutocomplete}
82
+ />
109
83
  );
110
- }
84
+ };
111
85
 
112
86
  WithAddNewOptions.args = {
113
87
  options,
114
88
  allowNewValues: true,
115
- shouldAutoComplete: true,
89
+ shouldAutocomplete: true,
116
90
  };
117
91
 
118
- export function MultiSelectWithAddNewOptions(props) {
92
+ export const MultiSelectWithAddNewOptions: StoryFunction = (props) => {
119
93
  const id = useId();
120
94
  return (
121
- <DemoContainer dataTheme={props.darkMode}>
122
- <UNSAFE_Combobox
123
- id={id}
124
- isMultiSelect={props.isMultiSelect}
125
- label="Multiselect komboboks med mulighet for å legge til nye verdier"
126
- options={props.options}
127
- allowNewValues={props.allowNewValues}
128
- />
129
- </DemoContainer>
95
+ <UNSAFE_Combobox
96
+ id={id}
97
+ isMultiSelect={props.isMultiSelect}
98
+ label="Multiselect komboboks med mulighet for å legge til nye verdier"
99
+ options={props.options}
100
+ allowNewValues={props.allowNewValues}
101
+ />
130
102
  );
131
- }
103
+ };
132
104
 
133
105
  MultiSelectWithAddNewOptions.args = {
134
106
  allowNewValues: true,
@@ -137,19 +109,20 @@ MultiSelectWithAddNewOptions.args = {
137
109
  shouldAutocomplete: false,
138
110
  };
139
111
 
140
- export const MultiSelectWithExternalChips = (props) => {
141
- const [selectedOptions, setSelectedOptions] = useState<string[]>(
142
- props.selectedOptions
143
- );
112
+ export const MultiSelectWithExternalChips: StoryFn<{
113
+ controlled: boolean;
114
+ options: ComboboxProps["options"];
115
+ }> = (props) => {
116
+ const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
144
117
  const [value, setValue] = useState("");
145
118
  const id = useId();
146
119
 
147
- const toggleSelected = (option) =>
120
+ const toggleSelected = (option: string) =>
148
121
  selectedOptions.includes(option)
149
122
  ? setSelectedOptions(selectedOptions.filter((opt) => opt !== option))
150
123
  : setSelectedOptions([...selectedOptions, option]);
151
124
  return (
152
- <DemoContainer dataTheme={props.darkMode}>
125
+ <>
153
126
  {selectedOptions && (
154
127
  <Chips>
155
128
  {selectedOptions.map((option) => (
@@ -164,84 +137,85 @@ export const MultiSelectWithExternalChips = (props) => {
164
137
  </Chips>
165
138
  )}
166
139
  <UNSAFE_Combobox
167
- options={options}
140
+ options={props.options}
168
141
  selectedOptions={selectedOptions}
169
- onToggleSelected={(option: string) => toggleSelected(option)}
170
- isListOpen={props.isListOpen}
142
+ onToggleSelected={(option) => toggleSelected(option)}
171
143
  isMultiSelect
172
144
  value={props.controlled ? value : undefined}
173
145
  onChange={(event) =>
174
- props.controlled ? setValue(event.currentTarget.value) : undefined
146
+ props.controlled
147
+ ? setValue(event?.currentTarget.value || "")
148
+ : undefined
175
149
  }
176
150
  label="Komboboks"
177
151
  size="medium"
178
- error={props.error && "error here"}
179
152
  id={id}
180
153
  shouldShowSelectedOptions={false}
181
154
  />
182
- </DemoContainer>
155
+ </>
183
156
  );
184
157
  };
185
158
 
186
159
  MultiSelectWithExternalChips.args = {
187
160
  controlled: false,
188
161
  options,
189
- selectedOptions: [],
190
162
  };
191
163
 
192
- export function Loading(props) {
164
+ export const Loading: StoryFunction = (props) => {
193
165
  const id = useId();
194
166
  return (
195
- <DemoContainer dataTheme={props.darkMode}>
196
- <UNSAFE_Combobox
197
- id={id}
198
- label="Komboboks (laster)"
199
- options={[]}
200
- selectedOptions={[]}
201
- isListOpen={props.isListOpen}
202
- isLoading={props.isLoading}
203
- />
204
- </DemoContainer>
167
+ <UNSAFE_Combobox
168
+ id={id}
169
+ label="Komboboks (laster)"
170
+ options={[]}
171
+ selectedOptions={[]}
172
+ isListOpen={props.isListOpen}
173
+ isLoading={props.isLoading}
174
+ />
205
175
  );
206
- }
176
+ };
207
177
 
208
178
  Loading.args = {
209
179
  isLoading: true,
210
180
  isListOpen: true,
211
181
  };
212
182
 
213
- export function ComboboxWithNoHits(props) {
183
+ export const ComboboxWithNoHits: StoryFunction = (props) => {
214
184
  const id = useId();
215
185
  const [value, setValue] = useState(props.value);
216
186
  return (
217
- <DemoContainer dataTheme={props.darkMode}>
218
- <UNSAFE_Combobox
219
- id={id}
220
- label="Komboboks (uten søketreff)"
221
- options={props.options}
222
- value={value}
223
- onChange={(event) => setValue(event.currentTarget.value)}
224
- isListOpen={true}
225
- />
226
- </DemoContainer>
187
+ <UNSAFE_Combobox
188
+ id={id}
189
+ label="Komboboks (uten søketreff)"
190
+ options={props.options}
191
+ value={value}
192
+ onChange={(event) => setValue(event?.currentTarget.value)}
193
+ isListOpen={true}
194
+ />
227
195
  );
228
- }
196
+ };
229
197
 
230
198
  ComboboxWithNoHits.args = {
231
199
  options,
232
200
  value: "Orange",
233
201
  };
234
202
 
235
- export const Controlled = (props) => {
203
+ export const Controlled: StoryFn<{
204
+ value: string;
205
+ options: string[];
206
+ initialSelectedOptions: string[];
207
+ }> = (props) => {
236
208
  const id = useId();
237
209
  const [value, setValue] = useState(props.value);
238
- const [selectedOptions, setSelectedOptions] = useState(props.selectedOptions);
210
+ const [selectedOptions, setSelectedOptions] = useState(
211
+ props.initialSelectedOptions
212
+ );
239
213
  const filteredOptions = useMemo(
240
214
  () => props.options.filter((option) => option.includes(value)),
241
215
  [props.options, value]
242
216
  );
243
217
 
244
- const onToggleSelected = (option, isSelected) => {
218
+ const onToggleSelected = (option: string, isSelected: boolean) => {
245
219
  if (isSelected) {
246
220
  setSelectedOptions([...selectedOptions, option]);
247
221
  } else {
@@ -250,7 +224,7 @@ export const Controlled = (props) => {
250
224
  };
251
225
 
252
226
  return (
253
- <DemoContainer dataTheme={props.darkMode}>
227
+ <>
254
228
  <TextField
255
229
  label="Overstyr value"
256
230
  onChange={(event) => setValue(event.target.value)}
@@ -263,23 +237,23 @@ export const Controlled = (props) => {
263
237
  filteredOptions={filteredOptions}
264
238
  isMultiSelect
265
239
  options={props.options}
266
- onChange={(event) => setValue(event.target.value)}
240
+ onChange={(event) => setValue(event?.target.value || "")}
267
241
  onToggleSelected={onToggleSelected}
268
242
  selectedOptions={selectedOptions}
269
243
  value={value}
270
244
  />
271
- </DemoContainer>
245
+ </>
272
246
  );
273
247
  };
274
248
 
275
249
  Controlled.args = {
276
250
  value: "apple",
277
251
  options,
278
- selectedOptions: initialSelectedOptions,
252
+ initialSelectedOptions: ["passion fruit", "grape fruit"],
279
253
  };
280
254
 
281
- export const ComboboxSizes = (props) => (
282
- <DemoContainer dataTheme={props.darkMode}>
255
+ export const ComboboxSizes = () => (
256
+ <>
283
257
  <UNSAFE_Combobox
284
258
  label="Hva er dine favorittfrukter?"
285
259
  description="Medium single-select"
@@ -309,36 +283,29 @@ export const ComboboxSizes = (props) => (
309
283
  size="small"
310
284
  allowNewValues
311
285
  />
312
- </DemoContainer>
286
+ </>
313
287
  );
314
288
 
315
- ComboboxSizes.args = {
316
- options,
317
- };
318
-
319
- export const WithError = {
289
+ export const WithError: StoryObject = {
320
290
  args: {
321
291
  error: "Du må velge en favorittfrukt.",
322
- isLoading: true,
323
292
  },
324
293
  render: (props) => {
325
294
  const [hasSelectedValue, setHasSelectedValue] = useState(false);
326
295
  const [isLoading, setIsLoading] = useState(false);
327
296
  return (
328
- <DemoContainer dataTheme={props.darkMode}>
329
- <UNSAFE_Combobox
330
- filteredOptions={isLoading ? [] : undefined}
331
- options={options}
332
- label="Hva er dine favorittfrukter?"
333
- error={!hasSelectedValue && props.error}
334
- isLoading={isLoading}
335
- onChange={() => {
336
- setIsLoading(true);
337
- setTimeout(() => setIsLoading(false), 2000);
338
- }}
339
- onToggleSelected={(_, isSelected) => setHasSelectedValue(isSelected)}
340
- />
341
- </DemoContainer>
297
+ <UNSAFE_Combobox
298
+ filteredOptions={isLoading ? [] : undefined}
299
+ options={options}
300
+ label="Hva er dine favorittfrukter?"
301
+ error={!hasSelectedValue && props.error}
302
+ isLoading={isLoading}
303
+ onChange={() => {
304
+ setIsLoading(true);
305
+ setTimeout(() => setIsLoading(false), 2000);
306
+ }}
307
+ onToggleSelected={(_, isSelected) => setHasSelectedValue(isSelected)}
308
+ />
342
309
  );
343
310
  },
344
311
  };
@@ -347,15 +314,10 @@ function sleep(ms: number) {
347
314
  return new Promise((resolve) => setTimeout(resolve, ms));
348
315
  }
349
316
 
350
- export const CancelInputTest = {
351
- render: (props) => {
317
+ export const CancelInputTest: StoryObject = {
318
+ render: () => {
352
319
  return (
353
- <DemoContainer dataTheme={props.darkMode}>
354
- <UNSAFE_Combobox
355
- options={options}
356
- label="Hva er dine favorittfrukter?"
357
- />
358
- </DemoContainer>
320
+ <UNSAFE_Combobox options={options} label="Hva er dine favorittfrukter?" />
359
321
  );
360
322
  },
361
323
  play: async ({ canvasElement }) => {
@@ -378,16 +340,14 @@ export const CancelInputTest = {
378
340
  },
379
341
  };
380
342
 
381
- export const RemoveSelectedMultiSelectTest = {
382
- render: (props) => {
343
+ export const RemoveSelectedMultiSelectTest: StoryObject = {
344
+ render: () => {
383
345
  return (
384
- <DemoContainer dataTheme={props.darkMode}>
385
- <UNSAFE_Combobox
386
- options={options}
387
- label="Hva er dine favorittfrukter?"
388
- isMultiSelect
389
- />
390
- </DemoContainer>
346
+ <UNSAFE_Combobox
347
+ options={options}
348
+ label="Hva er dine favorittfrukter?"
349
+ isMultiSelect
350
+ />
391
351
  );
392
352
  },
393
353
  play: async ({ canvasElement }) => {
@@ -436,16 +396,14 @@ export const RemoveSelectedMultiSelectTest = {
436
396
  },
437
397
  };
438
398
 
439
- export const AddWhenAddNewDisabledTest = {
440
- render: (props) => {
399
+ export const AddWhenAddNewDisabledTest: StoryObject = {
400
+ render: () => {
441
401
  return (
442
- <DemoContainer dataTheme={props.darkMode}>
443
- <UNSAFE_Combobox
444
- options={options}
445
- label="Hva er dine favorittfrukter?"
446
- isMultiSelect
447
- />
448
- </DemoContainer>
402
+ <UNSAFE_Combobox
403
+ options={options}
404
+ label="Hva er dine favorittfrukter?"
405
+ isMultiSelect
406
+ />
449
407
  );
450
408
  },
451
409
  play: async ({ canvasElement }) => {
@@ -471,7 +429,11 @@ export const AddWhenAddNewDisabledTest = {
471
429
  },
472
430
  };
473
431
 
474
- export const TestThatCallbacksOnlyFireWhenExpected = {
432
+ export const TestThatCallbacksOnlyFireWhenExpected: StoryObj<{
433
+ onChange: ReturnType<typeof jest.fn>;
434
+ onClear: ReturnType<typeof jest.fn>;
435
+ onToggleSelected: ReturnType<typeof jest.fn>;
436
+ }> = {
475
437
  args: {
476
438
  onChange: jest.fn(),
477
439
  onClear: jest.fn(),
@@ -479,13 +441,11 @@ export const TestThatCallbacksOnlyFireWhenExpected = {
479
441
  },
480
442
  render: (props) => {
481
443
  return (
482
- <DemoContainer dataTheme={props.darkMode}>
483
- <UNSAFE_Combobox
484
- options={options}
485
- label="Hva er dine favorittfrukter?"
486
- {...props}
487
- />
488
- </DemoContainer>
444
+ <UNSAFE_Combobox
445
+ options={options}
446
+ label="Hva er dine favorittfrukter?"
447
+ {...props}
448
+ />
489
449
  );
490
450
  },
491
451
  play: async ({ canvasElement, args }) => {
@@ -510,16 +470,10 @@ export const TestThatCallbacksOnlyFireWhenExpected = {
510
470
  },
511
471
  };
512
472
 
513
- export const TestHoverAndFocusSwitching = {
514
- render: (props) => {
473
+ export const TestHoverAndFocusSwitching: StoryObject = {
474
+ render: () => {
515
475
  return (
516
- <DemoContainer dataTheme={props.darkMode}>
517
- <UNSAFE_Combobox
518
- options={options}
519
- label="Hva er dine favorittfrukter?"
520
- {...props}
521
- />
522
- </DemoContainer>
476
+ <UNSAFE_Combobox options={options} label="Hva er dine favorittfrukter?" />
523
477
  );
524
478
  },
525
479
  play: async ({ canvasElement }) => {
@@ -1,6 +1,6 @@
1
1
  import React, { forwardRef, HTMLAttributes } from "react";
2
- import Guide from "./Guide";
3
2
  import cl from "clsx";
3
+ import { DefaultIllustration } from "./Illustration";
4
4
 
5
5
  export interface GuidePanelProps extends HTMLAttributes<HTMLDivElement> {
6
6
  /**
@@ -12,14 +12,14 @@ export interface GuidePanelProps extends HTMLAttributes<HTMLDivElement> {
12
12
  */
13
13
  illustration?: React.ReactNode;
14
14
  /**
15
- * Poster positions guide-illustation above content
16
- * @default false, renders illustation left of content
15
+ * Render illustation above content
16
+ * @default true on mobile (<480px)
17
17
  */
18
18
  poster?: boolean;
19
19
  }
20
20
 
21
21
  /**
22
- * A component that displays a guide panel.
22
+ * A component for guiding users on the website
23
23
  *
24
24
  * @see [📝 Documentation](https://aksel.nav.no/komponenter/core/guidepanel)
25
25
  * @see 🏷️ {@link GuidePanelProps}
@@ -34,18 +34,19 @@ export interface GuidePanelProps extends HTMLAttributes<HTMLDivElement> {
34
34
  * ```
35
35
  */
36
36
  export const GuidePanel = forwardRef<HTMLDivElement, GuidePanelProps>(
37
- (
38
- { children, className, illustration, poster = false, color, ...rest },
39
- ref
40
- ) => (
37
+ ({ children, className, illustration, poster, ...rest }, ref) => (
41
38
  <div
42
39
  {...rest}
43
40
  ref={ref}
44
41
  className={cl("navds-guide-panel", className, {
45
- "navds-guide-panel--poster": poster,
42
+ "navds-guide-panel--poster": poster === true,
43
+ "navds-guide-panel--not-poster": poster === false,
44
+ "navds-guide-panel--responsive-poster": poster === undefined,
46
45
  })}
47
46
  >
48
- <Guide size={poster ? "medium" : "small"} illustration={illustration} />
47
+ <div className="navds-guide">
48
+ {illustration ?? <DefaultIllustration />}
49
+ </div>
49
50
  <div className="navds-guide-panel__content">{children}</div>
50
51
  </div>
51
52
  )