@altinn/altinn-components 0.4.1 → 0.5.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 (178) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/biome.jsonc +1 -1
  3. package/lib/components/Attachment/AttachmentLink.stories.ts +1 -1
  4. package/lib/components/Attachment/AttachmentList.stories.ts +1 -1
  5. package/lib/components/Button/Button.tsx +13 -10
  6. package/lib/components/Button/ButtonBase.tsx +1 -1
  7. package/lib/components/Button/ButtonIcon.tsx +16 -0
  8. package/lib/components/Button/ButtonLabel.tsx +16 -0
  9. package/lib/components/Button/Buttons.stories.tsx +64 -0
  10. package/lib/components/Button/ComboButton.tsx +9 -7
  11. package/lib/components/Button/IconButton.stories.tsx +47 -0
  12. package/lib/components/Button/IconButton.tsx +15 -5
  13. package/lib/components/Button/button.module.css +5 -46
  14. package/lib/components/Button/buttonBase.module.css +55 -23
  15. package/lib/components/Button/buttonIcon.module.css +17 -0
  16. package/lib/components/Button/buttonLabel.module.css +17 -0
  17. package/lib/components/Button/comboButton.module.css +15 -65
  18. package/lib/components/Button/iconButton.module.css +21 -4
  19. package/lib/components/Button/index.ts +2 -0
  20. package/lib/components/ContextMenu/ContextMenu.stories.ts +49 -0
  21. package/lib/components/ContextMenu/ContextMenu.tsx +12 -20
  22. package/lib/components/ContextMenu/ContextMenuBase.tsx +33 -0
  23. package/lib/components/Dialog/Dialog.tsx +2 -0
  24. package/lib/components/Dialog/DialogGroup.tsx +24 -0
  25. package/lib/components/Dialog/DialogList.stories.ts +14 -10
  26. package/lib/components/Dialog/DialogList.tsx +26 -11
  27. package/lib/components/Dialog/DialogListItem.tsx +12 -2
  28. package/lib/components/Dialog/DialogListItemBase.tsx +4 -2
  29. package/lib/components/Dialog/DialogNav.stories.ts +5 -5
  30. package/lib/components/Dialog/DialogNav.tsx +2 -6
  31. package/lib/components/Dialog/DialogSelect.tsx +1 -1
  32. package/lib/components/Dialog/dialogGroup.module.css +35 -0
  33. package/lib/components/Dropdown/Backdrop.tsx +4 -3
  34. package/lib/components/Dropdown/DrawerBase.tsx +5 -2
  35. package/lib/components/Dropdown/DrawerBody.tsx +12 -0
  36. package/lib/components/Dropdown/DrawerButton.tsx +17 -0
  37. package/lib/components/Dropdown/DrawerFooter.tsx +12 -0
  38. package/lib/components/Dropdown/DrawerHeader.tsx +19 -0
  39. package/lib/components/Dropdown/DrawerOrDropdown.tsx +29 -0
  40. package/lib/components/Dropdown/DropdownBase.tsx +5 -2
  41. package/lib/components/Dropdown/backdrop.module.css +3 -0
  42. package/lib/components/Dropdown/drawerBase.module.css +9 -0
  43. package/lib/components/Dropdown/drawerBody.module.css +5 -0
  44. package/lib/components/Dropdown/drawerButton.module.css +6 -0
  45. package/lib/components/Dropdown/drawerFooter.module.css +13 -0
  46. package/lib/components/Dropdown/drawerHeader.module.css +17 -0
  47. package/lib/components/Dropdown/drawerOrDropdown.module.css +19 -0
  48. package/lib/components/Dropdown/dropdownBase.module.css +15 -3
  49. package/lib/components/Dropdown/index.ts +7 -1
  50. package/lib/components/Footer/footerMenu.module.css +5 -0
  51. package/lib/components/GlobalMenu/GlobalMenu.stories.tsx +9 -9
  52. package/lib/components/GlobalMenu/GlobalMenu.tsx +5 -5
  53. package/lib/components/Header/{Header.stories.ts → Header.stories.tsx} +79 -20
  54. package/lib/components/Header/Header.tsx +24 -37
  55. package/lib/components/Header/HeaderBase.tsx +7 -3
  56. package/lib/components/Header/header.module.css +10 -42
  57. package/lib/components/Header/headerBase.module.css +43 -0
  58. package/lib/components/Header/headerButton.module.css +1 -0
  59. package/lib/components/Layout/Layout.stories.tsx +77 -38
  60. package/lib/components/Layout/Layout.tsx +5 -3
  61. package/lib/components/Layout/LayoutBase.tsx +3 -2
  62. package/lib/components/Layout/layoutBase.module.css +11 -0
  63. package/lib/components/Layout/layoutBody.module.css +1 -0
  64. package/lib/components/LayoutAction/ActionHeader.tsx +1 -1
  65. package/lib/components/LayoutAction/ActionMenu.tsx +2 -4
  66. package/lib/components/LayoutAction/actionMenu.module.css +3 -0
  67. package/lib/components/List/List.stories.tsx +43 -0
  68. package/lib/components/List/List.tsx +6 -6
  69. package/lib/components/List/ListBase.tsx +6 -6
  70. package/lib/components/List/ListItem.tsx +4 -1
  71. package/lib/components/List/ListItemBase.tsx +20 -4
  72. package/lib/components/List/listBase.module.css +3 -3
  73. package/lib/components/List/listItemBase.module.css +4 -0
  74. package/lib/components/Menu/Menu.stories.ts +46 -46
  75. package/lib/components/Menu/Menu.tsx +3 -102
  76. package/lib/components/Menu/MenuBase.tsx +47 -3
  77. package/lib/components/Menu/MenuItem.tsx +6 -4
  78. package/lib/components/Menu/MenuItemBase.tsx +7 -0
  79. package/lib/components/Menu/MenuItems.stories.ts +438 -0
  80. package/lib/components/Menu/MenuItems.tsx +96 -0
  81. package/lib/components/Menu/MenuOption.tsx +4 -1
  82. package/lib/components/Menu/{MenuGroup.tsx → __MenuGroup.tsx} +1 -1
  83. package/lib/components/Menu/index.ts +1 -1
  84. package/lib/components/Menu/menu.module.css +2 -3
  85. package/lib/components/Menu/menuBase.module.css +25 -0
  86. package/lib/components/Menu/menuItemBase.module.css +5 -4
  87. package/lib/components/Meta/MetaItemBase.tsx +1 -1
  88. package/lib/components/Meta/MetaItemLabel.tsx +1 -1
  89. package/lib/components/Meta/MetaItemMedia.tsx +1 -1
  90. package/lib/components/Page/PageBase.tsx +14 -0
  91. package/lib/components/Page/PageHeader.tsx +21 -0
  92. package/lib/components/Page/PageHeaderMedia.tsx +25 -0
  93. package/lib/components/Page/SectionBase.tsx +52 -0
  94. package/lib/components/Page/SectionFooter.tsx +15 -0
  95. package/lib/components/Page/SectionHeader.tsx +16 -0
  96. package/lib/components/Page/index.ts +5 -0
  97. package/lib/components/Page/pageHeader.module.css +5 -0
  98. package/lib/components/Page/sectionBase.module.css +82 -0
  99. package/lib/components/Page/sectionFooter.module.css +8 -0
  100. package/lib/components/Page/sectionHeader.module.css +9 -0
  101. package/lib/components/RootProvider/RootProvider.tsx +43 -7
  102. package/lib/components/Searchbar/Autocomplete.stories.tsx +77 -0
  103. package/lib/components/Searchbar/Autocomplete.tsx +44 -0
  104. package/lib/components/Searchbar/AutocompleteBase.tsx +16 -0
  105. package/lib/components/Searchbar/AutocompleteGroup.tsx +17 -0
  106. package/lib/components/Searchbar/AutocompleteItem.tsx +23 -0
  107. package/lib/components/Searchbar/SearchField.tsx +78 -0
  108. package/lib/components/Searchbar/Searchbar.stories.tsx +151 -0
  109. package/lib/components/Searchbar/Searchbar.tsx +18 -0
  110. package/lib/components/Searchbar/SearchbarBase.tsx +23 -0
  111. package/lib/components/Searchbar/autocompleteBase.module.css +17 -0
  112. package/lib/components/Searchbar/autocompleteGroup.module.css +3 -0
  113. package/lib/components/Searchbar/autocompleteItem.module.css +19 -0
  114. package/lib/components/Searchbar/index.ts +1 -0
  115. package/lib/components/Searchbar/searchField.module.css +54 -0
  116. package/lib/components/Searchbar/searchbarBase.module.css +20 -0
  117. package/lib/components/Toolbar/Toolbar.stories.tsx +10 -10
  118. package/lib/components/Toolbar/Toolbar.tsx +28 -10
  119. package/lib/components/Toolbar/ToolbarAdd.tsx +6 -5
  120. package/lib/components/Toolbar/ToolbarBase.tsx +5 -20
  121. package/lib/components/Toolbar/ToolbarButton.tsx +1 -1
  122. package/lib/components/Toolbar/ToolbarFilter.tsx +11 -6
  123. package/lib/components/Toolbar/ToolbarMenu.tsx +8 -7
  124. package/lib/components/Toolbar/ToolbarOptions.stories.ts +5 -5
  125. package/lib/components/Toolbar/ToolbarOptions.tsx +34 -21
  126. package/lib/components/Toolbar/toolbarAdd.module.css +7 -0
  127. package/lib/components/Toolbar/toolbarBase.module.css +19 -0
  128. package/lib/components/Toolbar/toolbarButton.module.css +1 -1
  129. package/lib/components/Toolbar/toolbarFilter.module.css +25 -0
  130. package/lib/components/Toolbar/toolbarMenu.module.css +7 -0
  131. package/lib/components/Typography/Heading.tsx +23 -0
  132. package/lib/components/Typography/Typography.tsx +8 -5
  133. package/lib/components/Typography/heading.module.css +21 -0
  134. package/lib/components/Typography/index.ts +1 -0
  135. package/lib/components/Typography/typography.module.css +8 -0
  136. package/lib/components/index.ts +2 -0
  137. package/lib/hooks/index.ts +3 -0
  138. package/lib/{components/Menu → hooks}/useEscapeKey.ts +2 -2
  139. package/lib/hooks/useMenu.tsx +80 -0
  140. package/lib/index.ts +1 -0
  141. package/lib/stories/Color/MenuItem.stories.tsx +43 -0
  142. package/lib/stories/Color/Swatches.stories.tsx +19 -0
  143. package/lib/stories/Color/Swatches.tsx +42 -0
  144. package/lib/stories/Color/colors.json +62 -0
  145. package/lib/stories/Color/swatches.module.css +14 -0
  146. package/lib/stories/Inbox/BookmarksPage.tsx +52 -0
  147. package/lib/stories/Inbox/DialogPage.tsx +15 -0
  148. package/lib/stories/Inbox/Inbox.stories.tsx +55 -0
  149. package/lib/stories/Inbox/Inbox.tsx +12 -0
  150. package/lib/stories/Inbox/InboxLayout.tsx +50 -0
  151. package/lib/stories/Inbox/InboxPage.tsx +50 -0
  152. package/lib/stories/Inbox/InboxProvider.tsx +136 -0
  153. package/lib/stories/Inbox/InboxSection.tsx +39 -0
  154. package/lib/stories/Inbox/InboxToolbar.tsx +94 -0
  155. package/lib/stories/Inbox/ProfilePage.tsx +35 -0
  156. package/lib/stories/Inbox/SettingsPage.tsx +19 -0
  157. package/lib/stories/Inbox/accounts/accounts.ts +24 -0
  158. package/lib/stories/Inbox/accounts/index.ts +1 -0
  159. package/lib/stories/Inbox/actionMenu.ts +24 -0
  160. package/lib/stories/Inbox/dialogs/brreg-completed.json +35 -0
  161. package/lib/stories/Inbox/dialogs/brreg-draft.json +45 -0
  162. package/lib/stories/Inbox/dialogs/index.ts +10 -0
  163. package/lib/stories/Inbox/dialogs/skatt-2023.json +33 -0
  164. package/lib/stories/Inbox/groupBy.ts +19 -0
  165. package/lib/stories/Inbox/inboxSection.module.css +19 -0
  166. package/lib/stories/Inbox/index.ts +15 -0
  167. package/lib/stories/Inbox/layout/footer.ts +27 -0
  168. package/lib/stories/Inbox/layout/header.ts +11 -0
  169. package/lib/stories/Inbox/layout/index.ts +3 -0
  170. package/lib/stories/Inbox/layout/menu.ts +64 -0
  171. package/package.json +1 -1
  172. package/tsconfig.json +7 -2
  173. package/lib/components/Header/HeaderSearch.stories.ts +0 -20
  174. package/lib/components/Header/HeaderSearch.tsx +0 -44
  175. package/lib/components/Header/headerSearch.module.css +0 -30
  176. package/lib/components/Toolbar/toolbar.module.css +0 -43
  177. /package/lib/components/ContextMenu/{contextMenu.module.css → contextMenuBase.module.css} +0 -0
  178. /package/lib/{components/Menu → hooks}/useClickOutside.ts +0 -0
@@ -0,0 +1,151 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState } from 'react';
3
+ import { Searchbar } from './Searchbar';
4
+
5
+ const meta = {
6
+ title: 'Header/Searchbar',
7
+ component: Searchbar,
8
+ tags: ['autodocs'],
9
+ parameters: {},
10
+ args: {
11
+ placeholder: 'Search',
12
+ name: 'search',
13
+ expanded: false,
14
+ },
15
+ } satisfies Meta<typeof Searchbar>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Default: Story = {
21
+ args: {},
22
+ };
23
+
24
+ export const Query: Story = {
25
+ args: {
26
+ value: 'query',
27
+ },
28
+ };
29
+
30
+ export const Expanded: Story = {
31
+ args: {
32
+ placeholder: 'Søk i innboks',
33
+ expanded: true,
34
+ autocomplete: {
35
+ groups: {
36
+ 2: {
37
+ title: '2 treff i innboks',
38
+ },
39
+ },
40
+ items: [
41
+ {
42
+ id: '1a',
43
+ groupId: '1',
44
+ href: '#',
45
+ label: 'Alt i innboks',
46
+ },
47
+ {
48
+ id: '1b',
49
+ groupId: '1',
50
+ href: '#',
51
+ label: 'Alt i hele Altinn',
52
+ },
53
+ {
54
+ id: '2a',
55
+ groupId: '2',
56
+ href: '#',
57
+ label: 'Skattemelding 2024',
58
+ },
59
+ {
60
+ id: '2b',
61
+ groupId: '2',
62
+ href: '#',
63
+ label: 'Skattemelding 2025',
64
+ },
65
+ ],
66
+ },
67
+ },
68
+ };
69
+
70
+ export const ControlledState = (args) => {
71
+ const [expanded, setExpanded] = useState(false);
72
+ const [q, setQ] = useState<string>('');
73
+ const onChange = (event) => {
74
+ setQ(event.target.value);
75
+ };
76
+
77
+ const onFocus = () => {
78
+ setExpanded(true);
79
+ };
80
+
81
+ const scopes = [
82
+ {
83
+ groupId: '1',
84
+ id: 'inbox',
85
+ href: '#',
86
+ label: q
87
+ ? () => {
88
+ return (
89
+ <span>
90
+ <mark>{q}</mark> i innboksen
91
+ </span>
92
+ );
93
+ }
94
+ : 'Alt i innboksen',
95
+ },
96
+ {
97
+ groupId: '1',
98
+ id: 'global',
99
+ href: '#',
100
+ label: q
101
+ ? () => {
102
+ return (
103
+ <span>
104
+ <mark>{q}</mark> i hele Altinn
105
+ </span>
106
+ );
107
+ }
108
+ : 'Alt i hele Altinn',
109
+ },
110
+ ];
111
+
112
+ const suggestions = q
113
+ ? [
114
+ {
115
+ groupId: '2',
116
+ href: 'http://www.altinn.no',
117
+ label: 'Skattemelding 2024',
118
+ },
119
+ {
120
+ groupId: '2',
121
+ onClick: () => {
122
+ alert('Skattemelding 2025 ble trykket på');
123
+ },
124
+ label: 'Skattemelding 2025',
125
+ },
126
+ ].filter((item) => item.label.toLowerCase().includes((q ?? '').toLowerCase()))
127
+ : [];
128
+
129
+ const autocomplete = {
130
+ groups: {
131
+ 2: {
132
+ title: `${suggestions.length} treff i innboksen`,
133
+ },
134
+ },
135
+ items: [...scopes, ...suggestions],
136
+ };
137
+
138
+ return (
139
+ <Searchbar
140
+ {...args}
141
+ autocomplete={autocomplete}
142
+ expanded={expanded}
143
+ value={q}
144
+ onChange={onChange}
145
+ onFocus={onFocus}
146
+ onEnter={() => {
147
+ alert(`Søk etter ${q}`);
148
+ }}
149
+ />
150
+ );
151
+ };
@@ -0,0 +1,18 @@
1
+ import { Autocomplete, type AutocompleteProps } from './Autocomplete';
2
+ import { SearchField, type SearchFieldProps } from './SearchField';
3
+ import { SearchbarBase } from './SearchbarBase';
4
+
5
+ export interface SearchbarProps extends SearchFieldProps {
6
+ className?: string;
7
+ autocomplete?: AutocompleteProps;
8
+ expanded: boolean;
9
+ }
10
+
11
+ export const Searchbar = ({ className, autocomplete, expanded = false, onClose, ...search }: SearchbarProps) => {
12
+ return (
13
+ <SearchbarBase className={className} expanded={expanded} autocomplete={!!autocomplete}>
14
+ <SearchField {...search} expanded={expanded} onClose={onClose} />
15
+ {autocomplete && <Autocomplete {...autocomplete} expanded={expanded} />}
16
+ </SearchbarBase>
17
+ );
18
+ };
@@ -0,0 +1,23 @@
1
+ import cx from 'classnames';
2
+ import type { ReactNode } from 'react';
3
+ import styles from './searchbarBase.module.css';
4
+
5
+ export interface SearchbarBaseProps {
6
+ className?: string;
7
+ children: ReactNode;
8
+ expanded?: boolean;
9
+ autocomplete?: boolean;
10
+ }
11
+
12
+ export const SearchbarBase = ({ className, children, expanded = false, autocomplete = false }: SearchbarBaseProps) => {
13
+ return (
14
+ <div
15
+ className={cx(styles.searchbar, className)}
16
+ aria-expanded={expanded}
17
+ data-autocomplete={autocomplete}
18
+ data-theme="neutral"
19
+ >
20
+ {children}
21
+ </div>
22
+ );
23
+ };
@@ -0,0 +1,17 @@
1
+ .autocomplete {
2
+ width: 100%;
3
+ border: 2px solid;
4
+ border-radius: 0.25rem;
5
+ background-color: var(--theme-background-default);
6
+ }
7
+
8
+ .autocomplete ul {
9
+ list-style: none;
10
+ padding: 0;
11
+ margin: 0;
12
+ }
13
+
14
+ .autocomplete li {
15
+ padding: 0;
16
+ margin: 0.5rem 0;
17
+ }
@@ -0,0 +1,3 @@
1
+ .group + .group {
2
+ border-top: 1px solid var(--theme-border-subtle);
3
+ }
@@ -0,0 +1,19 @@
1
+ .item {
2
+ box-shadow: none;
3
+ }
4
+
5
+ .item mark {
6
+ background-color: transparent;
7
+ font-weight: 500;
8
+ text-decoration: underline;
9
+ text-decoration-thickness: 1px;
10
+ text-underline-offset: 4px;
11
+ }
12
+
13
+ .item mark:before {
14
+ content: "«";
15
+ }
16
+
17
+ .item mark:after {
18
+ content: "»";
19
+ }
@@ -0,0 +1 @@
1
+ export * from './Searchbar';
@@ -0,0 +1,54 @@
1
+ .field {
2
+ position: relative;
3
+ display: flex;
4
+ align-items: center;
5
+ font-size: 1.125rem;
6
+ background-color: transparent;
7
+ color: currentColor;
8
+ }
9
+
10
+ .icon {
11
+ position: absolute;
12
+ left: 0;
13
+ font-size: 1.5rem;
14
+ margin: 0 1rem;
15
+ }
16
+
17
+ .input {
18
+ height: 3.5rem; /* 56px */
19
+ background-color: transparent;
20
+ font-size: inherit;
21
+ flex-grow: 1;
22
+ padding-left: 3rem;
23
+ padding-right: 1rem;
24
+ padding-top: 1rem;
25
+ padding-bottom: 1rem;
26
+ border: 2px solid;
27
+ border-radius: 0.25rem;
28
+ outline: none;
29
+ }
30
+
31
+ .input[type="search"]::-webkit-search-decoration,
32
+ .input[type="search"]::-webkit-search-cancel-button {
33
+ appearance: none;
34
+ }
35
+
36
+ .input:focus,
37
+ .input[value] {
38
+ background-color: var(--theme-background-default);
39
+ }
40
+
41
+ .dismiss {
42
+ position: absolute;
43
+ right: 0;
44
+ margin: 0.375rem;
45
+ }
46
+
47
+ .clear {
48
+ position: absolute;
49
+ right: 0;
50
+ margin: 1rem;
51
+ border-radius: 100%;
52
+ width: 1.5rem;
53
+ height: 1.5rem;
54
+ }
@@ -0,0 +1,20 @@
1
+ .searchbar {
2
+ position: relative;
3
+ }
4
+
5
+ .searchbar[data-autocomplete="true"][aria-expanded="true"] input {
6
+ border-bottom-left-radius: 0;
7
+ border-bottom-right-radius: 0;
8
+ }
9
+
10
+ .searchbar nav {
11
+ display: none;
12
+ }
13
+
14
+ .searchbar[aria-expanded="true"] nav {
15
+ position: absolute;
16
+ display: block;
17
+ border-top-left-radius: 0;
18
+ border-top-right-radius: 0;
19
+ margin-top: -2px;
20
+ }
@@ -81,27 +81,27 @@ export const Default: Story = {
81
81
  label: 'Velg status',
82
82
  options: [
83
83
  {
84
- group: '1',
84
+ groupId: '1',
85
85
  value: 'draft',
86
86
  label: 'Utkast',
87
87
  },
88
88
  {
89
- group: '1',
89
+ groupId: '1',
90
90
  value: 'sent',
91
91
  label: 'Sendt',
92
92
  },
93
93
  {
94
- group: '2',
94
+ groupId: '2',
95
95
  value: 'in-progress',
96
96
  label: 'Under arbeid',
97
97
  },
98
98
  {
99
- group: '2',
99
+ groupId: '2',
100
100
  value: 'requires-attention',
101
101
  label: 'Krever handling',
102
102
  },
103
103
  {
104
- group: '2',
104
+ groupId: '2',
105
105
  value: 'completed',
106
106
  label: 'Avsluttet',
107
107
  },
@@ -157,27 +157,27 @@ export const FilterAndSearch: Story = {
157
157
  label: 'Velg status',
158
158
  options: [
159
159
  {
160
- group: '1',
160
+ groupId: '1',
161
161
  value: 'draft',
162
162
  label: 'Utkast',
163
163
  },
164
164
  {
165
- group: '1',
165
+ groupId: '1',
166
166
  value: 'sent',
167
167
  label: 'Sendt',
168
168
  },
169
169
  {
170
- group: '2',
170
+ groupId: '2',
171
171
  value: 'in-progress',
172
172
  label: 'Under arbeid',
173
173
  },
174
174
  {
175
- group: '2',
175
+ groupId: '2',
176
176
  value: 'requires-attention',
177
177
  label: 'Krever handling',
178
178
  },
179
179
  {
180
- group: '2',
180
+ groupId: '2',
181
181
  value: 'completed',
182
182
  label: 'Avsluttet',
183
183
  },
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { useMemo, useState } from 'react';
3
+ import { useRootContext } from '../RootProvider';
3
4
  import { ToolbarAdd } from './ToolbarAdd';
4
5
  import { ToolbarBase } from './ToolbarBase';
5
6
  import { ToolbarFilter, type ToolbarFilterProps } from './ToolbarFilter.tsx';
@@ -32,35 +33,52 @@ export const Toolbar = ({
32
33
  menu,
33
34
  getFilterLabel,
34
35
  }: ToolbarProps) => {
36
+ const { currentId, openId, closeAll } = useRootContext();
35
37
  const [expandedItem, setExpandedItem] = useState<ExpandedItem>(null);
36
38
  const [localFilterState, setLocalFilterState] = useState<Record<string, ToolbarFilterProps['value']>>(
37
39
  filterState ?? {},
38
40
  );
39
41
  const changeFilterState = typeof onFilterStateChange === 'function' ? onFilterStateChange : setLocalFilterState;
40
42
  const applicableFilterState = filterState || localFilterState;
41
- const [hiddenFilterNames, setHiddenFilterNames] = useState<string[]>(
43
+
44
+ const [visibleFilterNames, setVisibleFilterNames] = useState<string[]>(
42
45
  filters
43
- ?.filter((item) => item.removable && typeof applicableFilterState[item.name] === 'undefined')
46
+ ?.filter((item) => !(item.removable && typeof applicableFilterState[item.name] === 'undefined'))
44
47
  .map((item) => item.name) ?? [],
45
48
  );
46
49
 
50
+ const hiddenFilterNames = filters?.filter((item) => !visibleFilterNames.includes(item.name));
51
+
47
52
  const visibleFilters = useMemo(
48
- () => filters.filter((item) => !hiddenFilterNames.includes(item.name)),
49
- [filters, hiddenFilterNames],
53
+ () =>
54
+ visibleFilterNames
55
+ .map((name) => {
56
+ return filters.find((item) => item.name === name);
57
+ })
58
+ .filter((item) => typeof item !== 'undefined'),
59
+ [filters, visibleFilterNames],
50
60
  );
61
+
51
62
  const hiddenFilters = useMemo(
52
- () => filters.filter((item) => hiddenFilterNames.includes(item.name)),
63
+ () => filters.filter((item) => hiddenFilterNames.includes(item)),
53
64
  [filters, hiddenFilterNames],
54
65
  );
55
66
 
56
67
  const onToggle = (type: ExpandedItemType, name: string) => {
57
68
  if (expandedItem?.name === name && expandedItem.type === type) {
69
+ closeAll();
58
70
  setExpandedItem(null);
59
71
  } else {
72
+ openId('toolbar');
60
73
  setExpandedItem({ name, type });
61
74
  }
62
75
  };
63
76
 
77
+ const onClose = () => {
78
+ setExpandedItem(null);
79
+ closeAll();
80
+ };
81
+
64
82
  const onFilterChange = (name: string, value: ToolbarFilterProps['value'], optionType: ToolbarOptionType) => {
65
83
  if (optionType === 'radio') {
66
84
  changeFilterState({
@@ -81,7 +99,7 @@ export const Toolbar = ({
81
99
  };
82
100
 
83
101
  const onFilterRemove = (name: string) => {
84
- setHiddenFilterNames((prevState) => [...prevState, name]);
102
+ setVisibleFilterNames((prevState) => prevState.filter((prevName) => prevName !== name));
85
103
  changeFilterState({
86
104
  ...applicableFilterState,
87
105
  [name]: undefined,
@@ -89,19 +107,19 @@ export const Toolbar = ({
89
107
  };
90
108
 
91
109
  const onFilterAdd = (name: string) => {
110
+ setVisibleFilterNames((prevState) => [...prevState, name]);
92
111
  onToggle('filter', name);
93
- setHiddenFilterNames((prevState) => prevState.filter((prevName) => prevName !== name));
94
112
  };
95
113
 
96
114
  return (
97
- <ToolbarBase onClose={() => setExpandedItem(null)}>
115
+ <ToolbarBase open={currentId === 'toolbar'} onClose={onClose}>
98
116
  {menu && <ToolbarMenu onToggle={() => onToggle('menu', '')} expanded={expandedItem?.type === 'menu'} {...menu} />}
99
117
  {visibleFilters.map((item) => {
100
118
  return (
101
119
  <ToolbarFilter
102
120
  key={item.name}
103
121
  onToggle={() => onToggle('filter', item.name)}
104
- expanded={item.name === expandedItem?.name && expandedItem?.type === 'filter'}
122
+ expanded={currentId === 'toolbar' && item.name === expandedItem?.name && expandedItem?.type === 'filter'}
105
123
  onRemove={() => {
106
124
  onFilterRemove(item.name);
107
125
  }}
@@ -120,7 +138,7 @@ export const Toolbar = ({
120
138
  })}
121
139
  {hiddenFilters?.length > 0 && (
122
140
  <ToolbarAdd
123
- expanded={expandedItem?.type === 'add-filter'}
141
+ expanded={currentId === 'toolbar' && expandedItem?.type === 'add-filter'}
124
142
  onToggle={() => onToggle('add-filter', '')}
125
143
  items={hiddenFilters.map((item) => ({
126
144
  id: item.name,
@@ -1,7 +1,8 @@
1
1
  import type { MouseEventHandler } from 'react';
2
+ import { DrawerOrDropdown } from '../';
2
3
  import { Menu, type MenuItemProps } from '../Menu';
3
4
  import { ToolbarButton } from './ToolbarButton';
4
- import styles from './toolbar.module.css';
5
+ import styles from './toolbarAdd.module.css';
5
6
 
6
7
  export interface ToolbarAddProps {
7
8
  items: MenuItemProps[];
@@ -13,13 +14,13 @@ export interface ToolbarAddProps {
13
14
 
14
15
  export const ToolbarAdd = ({ expanded = false, onToggle, label = 'Legg til', items }: ToolbarAddProps) => {
15
16
  return (
16
- <div className={styles.toggle}>
17
- <ToolbarButton as="div" type="add" onToggle={onToggle}>
17
+ <div className={styles.menu}>
18
+ <ToolbarButton type="add" onToggle={onToggle}>
18
19
  {label}
19
20
  </ToolbarButton>
20
- <div aria-expanded={expanded} className={styles.dropdown}>
21
+ <DrawerOrDropdown title={label} onClose={onToggle} expanded={expanded}>
21
22
  <Menu theme="global" items={items} />
22
- </div>
23
+ </DrawerOrDropdown>
23
24
  </div>
24
25
  );
25
26
  };
@@ -1,27 +1,12 @@
1
1
  'use client';
2
- import { type ReactNode, useRef } from 'react';
3
- import { useClickOutside } from '../Menu/useClickOutside.ts';
4
- import { useEscapeKey } from '../Menu/useEscapeKey.ts';
5
- import styles from './toolbar.module.css';
2
+ import type { ReactNode } from 'react';
3
+ import styles from './toolbarBase.module.css';
6
4
  export interface ToolbarBaseProps {
7
5
  children?: ReactNode;
6
+ open?: boolean;
8
7
  onClose?: () => void;
9
8
  }
10
9
 
11
- export const ToolbarBase = ({ children, onClose }: ToolbarBaseProps) => {
12
- const ref = useRef<HTMLDivElement>(null);
13
-
14
- useClickOutside(ref, () => {
15
- onClose?.();
16
- });
17
-
18
- useEscapeKey(() => {
19
- onClose?.();
20
- });
21
-
22
- return (
23
- <div className={styles.toolbar} ref={ref}>
24
- {children}
25
- </div>
26
- );
10
+ export const ToolbarBase = ({ children }: ToolbarBaseProps) => {
11
+ return <div className={styles.toolbar}>{children}</div>;
27
12
  };
@@ -28,7 +28,7 @@ export const ToolbarButton = ({
28
28
  if (removable) {
29
29
  return (
30
30
  <ComboButton
31
- className={styles.remove}
31
+ className={styles.removeButton}
32
32
  variant={active ? 'solid' : 'outline'}
33
33
  color="primary"
34
34
  size="sm"
@@ -1,8 +1,9 @@
1
1
  import type { ChangeEventHandler, MouseEventHandler } from 'react';
2
+ import { DrawerOrDropdown } from '../';
2
3
  import type { MenuOptionProps } from '../Menu';
3
4
  import { ToolbarButton } from './ToolbarButton';
4
5
  import { ToolbarOptions } from './ToolbarOptions';
5
- import styles from './toolbar.module.css';
6
+ import styles from './toolbarFilter.module.css';
6
7
 
7
8
  type ToolbarFilterValue = (string | number)[];
8
9
  export interface ToolbarFilterProps {
@@ -28,7 +29,7 @@ const defaultGetSelectedLabel = (_: string, value?: ToolbarFilterValue) => {
28
29
  };
29
30
 
30
31
  export const ToolbarFilter = ({
31
- expanded,
32
+ expanded = false,
32
33
  removable,
33
34
  label,
34
35
  name,
@@ -51,9 +52,8 @@ export const ToolbarFilter = ({
51
52
  const valueLabel = getSelectedLabel?.(name, value) ?? defaultGetSelectedLabel(name, value);
52
53
 
53
54
  return (
54
- <div className={styles.toggle}>
55
+ <div className={styles.filter} aria-expanded={expanded}>
55
56
  <ToolbarButton
56
- as="div"
57
57
  type="select"
58
58
  removable={removable}
59
59
  active={Array.isArray(value) ? value.length > 0 : typeof value !== 'undefined'}
@@ -62,9 +62,14 @@ export const ToolbarFilter = ({
62
62
  >
63
63
  {valueLabel || label}
64
64
  </ToolbarButton>
65
- <div className={styles.dropdown} aria-expanded={expanded}>
65
+ <DrawerOrDropdown
66
+ expanded={expanded}
67
+ title={label}
68
+ onClose={onToggle}
69
+ button={{ onClick: onToggle, label: 'Vis X treff' }}
70
+ >
66
71
  <ToolbarOptions options={filterOptions} onChange={onChange} optionType={optionType} />
67
- </div>
72
+ </DrawerOrDropdown>
68
73
  </div>
69
74
  );
70
75
  };
@@ -1,14 +1,15 @@
1
1
  import type { MouseEventHandler } from 'react';
2
- import { Menu, type MenuGroups, type MenuItemProps, type MenuSearchProps } from '../Menu';
2
+ import { DrawerOrDropdown } from '../';
3
+ import { Menu, type MenuItemGroups, type MenuItemProps, type MenuSearchProps } from '../Menu';
3
4
  import { ToolbarButton } from './ToolbarButton';
4
- import styles from './toolbar.module.css';
5
+ import styles from './toolbarMenu.module.css';
5
6
 
6
7
  export interface ToolbarMenuProps {
7
8
  onToggle?: MouseEventHandler;
8
9
  label: string;
9
10
  value: string | number;
10
11
  items: MenuItemProps[];
11
- groups?: MenuGroups;
12
+ groups?: MenuItemGroups;
12
13
  search?: MenuSearchProps;
13
14
  expanded?: boolean;
14
15
  className?: string;
@@ -16,13 +17,13 @@ export interface ToolbarMenuProps {
16
17
 
17
18
  export const ToolbarMenu = ({ expanded = false, onToggle, label, value, groups, search, items }: ToolbarMenuProps) => {
18
19
  return (
19
- <div className={styles.toggle}>
20
- <ToolbarButton as="div" type="switch" onToggle={onToggle} active={!!value}>
20
+ <div className={styles.menu}>
21
+ <ToolbarButton type="switch" onToggle={onToggle} active={!!value}>
21
22
  {label}
22
23
  </ToolbarButton>
23
- <div className={styles.dropdown} aria-expanded={expanded}>
24
+ <DrawerOrDropdown expanded={expanded} title="Endre konto" onClose={onToggle}>
24
25
  <Menu theme="global" defaultItemColor="subtle" groups={groups} search={search} items={items} />
25
- </div>
26
+ </DrawerOrDropdown>
26
27
  </div>
27
28
  );
28
29
  };
@@ -74,31 +74,31 @@ export const RadioCheckbox: Story = {
74
74
  },
75
75
  options: [
76
76
  {
77
- group: 'a',
77
+ groupId: 'a',
78
78
  name: 'animal',
79
79
  label: 'Katt',
80
80
  value: 'cat',
81
81
  checked: true,
82
82
  },
83
83
  {
84
- group: 'a',
84
+ groupId: 'a',
85
85
  name: 'animal',
86
86
  label: 'Mus',
87
87
  value: 'mouse',
88
88
  },
89
89
  {
90
- group: 'a',
90
+ groupId: 'a',
91
91
  name: 'animal',
92
92
  label: 'Veggdyr',
93
93
  value: 'spider',
94
94
  },
95
95
  {
96
- group: 'b',
96
+ groupId: 'b',
97
97
  label: 'Husarrest',
98
98
  value: 'digdir',
99
99
  },
100
100
  {
101
- group: 'b',
101
+ groupId: 'b',
102
102
  checked: true,
103
103
  label: 'Piskeslag',
104
104
  value: 'helse',