@altinn/altinn-components 0.4.1 → 0.5.1

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 (194) hide show
  1. package/.storybook/StoryDecorator.tsx +1 -1
  2. package/.storybook/main.ts +3 -4
  3. package/.storybook/preview.tsx +28 -0
  4. package/CHANGELOG.md +14 -0
  5. package/biome.jsonc +1 -1
  6. package/lib/components/Attachment/AttachmentLink.stories.ts +1 -1
  7. package/lib/components/Attachment/AttachmentList.stories.ts +1 -1
  8. package/lib/components/Button/Button.tsx +13 -10
  9. package/lib/components/Button/ButtonBase.tsx +1 -1
  10. package/lib/components/Button/ButtonIcon.tsx +16 -0
  11. package/lib/components/Button/ButtonLabel.tsx +18 -0
  12. package/lib/components/Button/Buttons.stories.tsx +64 -0
  13. package/lib/components/Button/ComboButton.tsx +9 -7
  14. package/lib/components/Button/IconButton.stories.tsx +47 -0
  15. package/lib/components/Button/IconButton.tsx +15 -5
  16. package/lib/components/Button/button.module.css +5 -46
  17. package/lib/components/Button/buttonBase.module.css +55 -23
  18. package/lib/components/Button/buttonIcon.module.css +17 -0
  19. package/lib/components/Button/buttonLabel.module.css +17 -0
  20. package/lib/components/Button/comboButton.module.css +15 -65
  21. package/lib/components/Button/iconButton.module.css +21 -4
  22. package/lib/components/Button/index.ts +2 -0
  23. package/lib/components/ContextMenu/ContextMenu.stories.ts +49 -0
  24. package/lib/components/ContextMenu/ContextMenu.tsx +12 -20
  25. package/lib/components/ContextMenu/ContextMenuBase.tsx +33 -0
  26. package/lib/components/Dialog/Dialog.stories.ts +12 -5
  27. package/lib/components/Dialog/Dialog.tsx +2 -0
  28. package/lib/components/Dialog/DialogGroup.tsx +24 -0
  29. package/lib/components/Dialog/DialogList.stories.ts +14 -10
  30. package/lib/components/Dialog/DialogList.tsx +26 -11
  31. package/lib/components/Dialog/DialogListItem.tsx +12 -2
  32. package/lib/components/Dialog/DialogListItemBase.tsx +4 -2
  33. package/lib/components/Dialog/DialogNav.stories.ts +5 -5
  34. package/lib/components/Dialog/DialogNav.tsx +2 -6
  35. package/lib/components/Dialog/DialogSelect.tsx +1 -1
  36. package/lib/components/Dialog/dialogGroup.module.css +35 -0
  37. package/lib/components/Dropdown/Backdrop.tsx +4 -3
  38. package/lib/components/Dropdown/DrawerBase.tsx +5 -2
  39. package/lib/components/Dropdown/DrawerBody.tsx +12 -0
  40. package/lib/components/Dropdown/DrawerButton.tsx +17 -0
  41. package/lib/components/Dropdown/DrawerFooter.tsx +12 -0
  42. package/lib/components/Dropdown/DrawerHeader.tsx +19 -0
  43. package/lib/components/Dropdown/DrawerOrDropdown.tsx +29 -0
  44. package/lib/components/Dropdown/DropdownBase.tsx +17 -2
  45. package/lib/components/Dropdown/backdrop.module.css +3 -0
  46. package/lib/components/Dropdown/drawerBase.module.css +9 -0
  47. package/lib/components/Dropdown/drawerBody.module.css +5 -0
  48. package/lib/components/Dropdown/drawerButton.module.css +6 -0
  49. package/lib/components/Dropdown/drawerFooter.module.css +13 -0
  50. package/lib/components/Dropdown/drawerHeader.module.css +17 -0
  51. package/lib/components/Dropdown/drawerOrDropdown.module.css +19 -0
  52. package/lib/components/Dropdown/dropdownBase.module.css +20 -4
  53. package/lib/components/Dropdown/index.ts +7 -1
  54. package/lib/components/Footer/footerMenu.module.css +5 -0
  55. package/lib/components/GlobalMenu/AccountButton.tsx +29 -0
  56. package/lib/components/GlobalMenu/AccountMenu.stories.tsx +65 -0
  57. package/lib/components/GlobalMenu/AccountMenu.tsx +73 -0
  58. package/lib/components/GlobalMenu/BackButton.tsx +10 -0
  59. package/lib/components/GlobalMenu/GlobalMenu.stories.tsx +112 -121
  60. package/lib/components/GlobalMenu/GlobalMenu.tsx +41 -89
  61. package/lib/components/GlobalMenu/GlobalMenuBase.tsx +22 -0
  62. package/lib/components/GlobalMenu/LogoutButton.tsx +19 -0
  63. package/lib/components/GlobalMenu/globalMenuBase.module.css +39 -0
  64. package/lib/components/GlobalMenu/index.tsx +1 -1
  65. package/lib/components/GlobalMenu/logoutButton.module.css +9 -0
  66. package/lib/components/Header/{Header.stories.ts → Header.stories.tsx} +79 -20
  67. package/lib/components/Header/Header.tsx +25 -38
  68. package/lib/components/Header/HeaderBase.tsx +7 -3
  69. package/lib/components/Header/header.module.css +10 -42
  70. package/lib/components/Header/headerBase.module.css +43 -0
  71. package/lib/components/Header/headerButton.module.css +1 -0
  72. package/lib/components/Layout/Layout.stories.tsx +77 -38
  73. package/lib/components/Layout/Layout.tsx +5 -3
  74. package/lib/components/Layout/LayoutBase.tsx +3 -2
  75. package/lib/components/Layout/layoutBase.module.css +11 -0
  76. package/lib/components/Layout/layoutBody.module.css +1 -0
  77. package/lib/components/LayoutAction/ActionHeader.tsx +1 -1
  78. package/lib/components/LayoutAction/ActionMenu.tsx +2 -4
  79. package/lib/components/LayoutAction/actionMenu.module.css +3 -0
  80. package/lib/components/List/List.stories.tsx +43 -0
  81. package/lib/components/List/List.tsx +6 -6
  82. package/lib/components/List/ListBase.tsx +6 -6
  83. package/lib/components/List/ListItem.tsx +4 -1
  84. package/lib/components/List/ListItemBase.tsx +20 -4
  85. package/lib/components/List/listBase.module.css +3 -3
  86. package/lib/components/List/listItemBase.module.css +4 -0
  87. package/lib/components/Menu/Menu.stories.ts +46 -46
  88. package/lib/components/Menu/Menu.tsx +3 -102
  89. package/lib/components/Menu/MenuBase.tsx +47 -3
  90. package/lib/components/Menu/MenuItem.tsx +8 -4
  91. package/lib/components/Menu/MenuItemBase.tsx +15 -2
  92. package/lib/components/Menu/MenuItems.stories.ts +438 -0
  93. package/lib/components/Menu/MenuItems.tsx +96 -0
  94. package/lib/components/Menu/MenuOption.tsx +4 -1
  95. package/lib/components/Menu/index.ts +1 -1
  96. package/lib/components/Menu/menu.module.css +2 -3
  97. package/lib/components/Menu/menuBase.module.css +25 -0
  98. package/lib/components/Menu/menuItemBase.module.css +11 -5
  99. package/lib/components/Menu/menuItemLabel.module.css +11 -1
  100. package/lib/components/Menu/menuSearch.module.css +1 -0
  101. package/lib/components/Meta/MetaItemBase.tsx +1 -1
  102. package/lib/components/Meta/MetaItemLabel.tsx +1 -1
  103. package/lib/components/Meta/MetaItemMedia.tsx +1 -1
  104. package/lib/components/Page/PageBase.tsx +14 -0
  105. package/lib/components/Page/PageHeader.tsx +21 -0
  106. package/lib/components/Page/PageHeaderMedia.tsx +25 -0
  107. package/lib/components/Page/SectionBase.tsx +52 -0
  108. package/lib/components/Page/SectionFooter.tsx +15 -0
  109. package/lib/components/Page/SectionHeader.tsx +16 -0
  110. package/lib/components/Page/index.ts +5 -0
  111. package/lib/components/Page/pageHeader.module.css +5 -0
  112. package/lib/components/Page/sectionBase.module.css +82 -0
  113. package/lib/components/Page/sectionFooter.module.css +8 -0
  114. package/lib/components/Page/sectionHeader.module.css +9 -0
  115. package/lib/components/RootProvider/RootProvider.tsx +43 -7
  116. package/lib/components/Searchbar/Autocomplete.stories.tsx +77 -0
  117. package/lib/components/Searchbar/Autocomplete.tsx +44 -0
  118. package/lib/components/Searchbar/AutocompleteBase.tsx +16 -0
  119. package/lib/components/Searchbar/AutocompleteGroup.tsx +17 -0
  120. package/lib/components/Searchbar/AutocompleteItem.tsx +23 -0
  121. package/lib/components/Searchbar/SearchField.tsx +78 -0
  122. package/lib/components/Searchbar/Searchbar.stories.tsx +151 -0
  123. package/lib/components/Searchbar/Searchbar.tsx +18 -0
  124. package/lib/components/Searchbar/SearchbarBase.tsx +23 -0
  125. package/lib/components/Searchbar/autocompleteBase.module.css +17 -0
  126. package/lib/components/Searchbar/autocompleteGroup.module.css +3 -0
  127. package/lib/components/Searchbar/autocompleteItem.module.css +19 -0
  128. package/lib/components/Searchbar/index.ts +1 -0
  129. package/lib/components/Searchbar/searchField.module.css +54 -0
  130. package/lib/components/Searchbar/searchbarBase.module.css +20 -0
  131. package/lib/components/Toolbar/Toolbar.stories.tsx +10 -10
  132. package/lib/components/Toolbar/Toolbar.tsx +28 -10
  133. package/lib/components/Toolbar/ToolbarAdd.tsx +6 -5
  134. package/lib/components/Toolbar/ToolbarBase.tsx +5 -20
  135. package/lib/components/Toolbar/ToolbarButton.tsx +1 -1
  136. package/lib/components/Toolbar/ToolbarFilter.tsx +11 -6
  137. package/lib/components/Toolbar/ToolbarMenu.tsx +8 -7
  138. package/lib/components/Toolbar/ToolbarOptions.stories.ts +5 -5
  139. package/lib/components/Toolbar/ToolbarOptions.tsx +34 -21
  140. package/lib/components/Toolbar/toolbarAdd.module.css +7 -0
  141. package/lib/components/Toolbar/toolbarBase.module.css +19 -0
  142. package/lib/components/Toolbar/toolbarButton.module.css +1 -1
  143. package/lib/components/Toolbar/toolbarFilter.module.css +25 -0
  144. package/lib/components/Toolbar/toolbarMenu.module.css +7 -0
  145. package/lib/components/Typography/Heading.tsx +23 -0
  146. package/lib/components/Typography/Typography.tsx +8 -5
  147. package/lib/components/Typography/heading.module.css +21 -0
  148. package/lib/components/Typography/index.ts +1 -0
  149. package/lib/components/Typography/typography.module.css +8 -0
  150. package/lib/components/index.ts +2 -0
  151. package/lib/hooks/index.ts +3 -0
  152. package/lib/{components/Menu → hooks}/useEscapeKey.ts +2 -2
  153. package/lib/hooks/useMenu.tsx +80 -0
  154. package/lib/index.ts +1 -0
  155. package/lib/stories/Color/MenuItem.stories.tsx +43 -0
  156. package/lib/stories/Color/Swatches.stories.tsx +19 -0
  157. package/lib/stories/Color/Swatches.tsx +42 -0
  158. package/lib/stories/Color/colors.json +62 -0
  159. package/lib/stories/Color/swatches.module.css +14 -0
  160. package/lib/stories/Inbox/BookmarksPage.tsx +52 -0
  161. package/lib/stories/Inbox/DialogPage.tsx +15 -0
  162. package/lib/stories/Inbox/Inbox.stories.tsx +55 -0
  163. package/lib/stories/Inbox/Inbox.tsx +12 -0
  164. package/lib/stories/Inbox/InboxLayout.tsx +50 -0
  165. package/lib/stories/Inbox/InboxPage.tsx +50 -0
  166. package/lib/stories/Inbox/InboxProvider.tsx +136 -0
  167. package/lib/stories/Inbox/InboxSection.tsx +39 -0
  168. package/lib/stories/Inbox/InboxToolbar.tsx +94 -0
  169. package/lib/stories/Inbox/ProfilePage.tsx +35 -0
  170. package/lib/stories/Inbox/SettingsPage.tsx +19 -0
  171. package/lib/stories/Inbox/accounts/accounts.ts +24 -0
  172. package/lib/stories/Inbox/accounts/index.ts +1 -0
  173. package/lib/stories/Inbox/actionMenu.ts +24 -0
  174. package/lib/stories/Inbox/dialogs/brreg-completed.json +35 -0
  175. package/lib/stories/Inbox/dialogs/brreg-draft.json +45 -0
  176. package/lib/stories/Inbox/dialogs/index.ts +10 -0
  177. package/lib/stories/Inbox/dialogs/skatt-2023.json +33 -0
  178. package/lib/stories/Inbox/groupBy.ts +19 -0
  179. package/lib/stories/Inbox/inboxSection.module.css +19 -0
  180. package/lib/stories/Inbox/index.ts +15 -0
  181. package/lib/stories/Inbox/layout/footer.ts +27 -0
  182. package/lib/stories/Inbox/layout/header.ts +11 -0
  183. package/lib/stories/Inbox/layout/index.ts +3 -0
  184. package/lib/stories/Inbox/layout/menu.ts +64 -0
  185. package/package.json +15 -13
  186. package/tsconfig.json +7 -2
  187. package/lib/components/Header/HeaderSearch.stories.ts +0 -20
  188. package/lib/components/Header/HeaderSearch.tsx +0 -44
  189. package/lib/components/Header/headerSearch.module.css +0 -30
  190. package/lib/components/Menu/MenuGroup.tsx +0 -18
  191. package/lib/components/Menu/__menuItem.module.css +0 -130
  192. package/lib/components/Toolbar/toolbar.module.css +0 -43
  193. /package/lib/components/ContextMenu/{contextMenu.module.css → contextMenuBase.module.css} +0 -0
  194. /package/lib/{components/Menu → hooks}/useClickOutside.ts +0 -0
@@ -1,8 +1,9 @@
1
- import type { ChangeEventHandler } from 'react';
1
+ import { type ChangeEventHandler, Fragment } from 'react';
2
2
  import {
3
3
  MenuBase,
4
- MenuGroup,
5
4
  MenuHeader,
5
+ MenuList,
6
+ MenuListItem,
6
7
  MenuOption,
7
8
  type MenuOptionProps,
8
9
  MenuSearch,
@@ -13,6 +14,7 @@ export type ToolbarOptionType = 'checkbox' | 'radio';
13
14
 
14
15
  export interface OptionGroup {
15
16
  title?: string;
17
+ divider?: boolean;
16
18
  optionType?: ToolbarOptionType;
17
19
  }
18
20
 
@@ -24,7 +26,7 @@ export interface ToolbarOptionsProps {
24
26
  optionGroups?: { [key: string]: OptionGroup };
25
27
  }
26
28
 
27
- export const ToolbarOptions = ({ search, optionGroups, options, onChange, optionType }: ToolbarOptionsProps) => {
29
+ export const ToolbarOptions = ({ search, optionGroups = {}, options, onChange, optionType }: ToolbarOptionsProps) => {
28
30
  const sections = options.reduce(
29
31
  (acc, option) => {
30
32
  const group = option.group || '';
@@ -38,24 +40,35 @@ export const ToolbarOptions = ({ search, optionGroups, options, onChange, option
38
40
  return (
39
41
  <MenuBase theme="global">
40
42
  {search && <MenuSearch {...search} />}
41
- {Object.keys(sections)?.map((key) => {
42
- const headerTitle = optionGroups?.[key]?.title;
43
- return (
44
- <MenuGroup key={key}>
45
- {headerTitle && <MenuHeader title={headerTitle} />}
46
- {sections[key]?.map((item) => (
47
- <MenuOption
48
- key={item.value}
49
- onChange={onChange}
50
- label={item.label}
51
- type={optionGroups?.[key]?.optionType || optionType}
52
- value={item.value}
53
- checked={item.checked}
54
- />
55
- ))}
56
- </MenuGroup>
57
- );
58
- })}
43
+ <MenuList>
44
+ {Object.keys(sections)?.map((key, groupIndex) => {
45
+ const groupProps = optionGroups[key] || {};
46
+ const { title, divider = true } = groupProps;
47
+ return (
48
+ <Fragment key={key}>
49
+ {groupIndex && divider ? <MenuListItem role="separator" /> : ''}
50
+
51
+ {title && (
52
+ <MenuListItem>
53
+ <MenuHeader title={title} />
54
+ </MenuListItem>
55
+ )}
56
+ {sections[key]?.map((item) => (
57
+ <MenuListItem key={item.value}>
58
+ <MenuOption
59
+ onChange={onChange}
60
+ label={item.label}
61
+ badge={item.badge}
62
+ type={optionGroups?.[key]?.optionType || optionType}
63
+ value={item.value}
64
+ checked={item.checked}
65
+ />
66
+ </MenuListItem>
67
+ ))}
68
+ </Fragment>
69
+ );
70
+ })}
71
+ </MenuList>
59
72
  </MenuBase>
60
73
  );
61
74
  };
@@ -0,0 +1,7 @@
1
+ .menu {
2
+ position: relative;
3
+ }
4
+
5
+ .menu[aria-expanded="true"] {
6
+ z-index: 2;
7
+ }
@@ -0,0 +1,19 @@
1
+ .toolbar {
2
+ display: flex;
3
+ align-items: center;
4
+ flex-wrap: wrap;
5
+ width: 100%;
6
+ gap: 0.5rem;
7
+ /* padding: 0 1rem; */
8
+ }
9
+
10
+ @media (min-width: 1024px) {
11
+ .toolbar {
12
+ padding: 0;
13
+ /* margin: 1.125rem 0; */
14
+ }
15
+
16
+ .toolbar > * {
17
+ width: auto;
18
+ }
19
+ }
@@ -1,3 +1,3 @@
1
- .remove > button:hover + * + * {
1
+ .removeButton > button:hover + * + * {
2
2
  text-decoration: line-through;
3
3
  }
@@ -0,0 +1,25 @@
1
+ .filter {
2
+ position: relative;
3
+ }
4
+
5
+ .filter[aria-expanded="true"] {
6
+ z-index: 2;
7
+ }
8
+
9
+ .dropdown[aria-expanded="true"] {
10
+ display: none;
11
+ }
12
+
13
+ .drawer[aria-expanded="true"] {
14
+ display: block;
15
+ }
16
+
17
+ @media (min-width: 1024px) {
18
+ .drawer[aria-expanded="true"] {
19
+ display: none;
20
+ }
21
+
22
+ .dropdown[aria-expanded="true"] {
23
+ display: block;
24
+ }
25
+ }
@@ -0,0 +1,7 @@
1
+ .menu {
2
+ position: relative;
3
+ }
4
+
5
+ .menu[aria-expanded="true"] {
6
+ z-index: 2;
7
+ }
@@ -0,0 +1,23 @@
1
+ import cx from 'classnames';
2
+ import type { ReactNode } from 'react';
3
+ import styles from './heading.module.css';
4
+
5
+ export type HeadingSize = 'sm' | 'md' | 'lg';
6
+ export type HeadingComponent = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
7
+
8
+ export interface HeadingProps {
9
+ as?: HeadingComponent;
10
+ size?: HeadingSize;
11
+ className?: string;
12
+ children?: ReactNode;
13
+ }
14
+
15
+ export const Heading = ({ as = 'h2', size = 'md', className, children }: HeadingProps) => {
16
+ const H = as;
17
+
18
+ return (
19
+ <H className={cx(styles.heading, className)} data-size={size}>
20
+ {children}
21
+ </H>
22
+ );
23
+ };
@@ -1,21 +1,24 @@
1
1
  import cx from 'classnames';
2
- import type { ReactNode } from 'react';
2
+ import type { ElementType, ReactNode } from 'react';
3
3
  import type { LayoutTheme } from '../Layout';
4
4
  import styles from './typography.module.css';
5
5
 
6
- export type TypographySize = 'md' | 'lg' | 'xl';
6
+ export type TypographySize = 'sm' | 'md' | 'lg';
7
7
 
8
8
  export interface TypographyProps {
9
+ as?: ElementType;
9
10
  size?: TypographySize;
10
11
  theme?: LayoutTheme;
11
12
  className?: string;
12
13
  children?: ReactNode;
13
14
  }
14
15
 
15
- export const Typography = ({ size = 'md', theme, className, children }: TypographyProps) => {
16
+ export const Typography = ({ as = 'div', size = 'md', theme, className, children }: TypographyProps) => {
17
+ const Component = as;
18
+
16
19
  return (
17
- <div className={cx(styles.typography, className)} data-size={size} data-theme={theme}>
20
+ <Component className={cx(styles.typography, className)} data-size={size} data-theme={theme}>
18
21
  {children}
19
- </div>
22
+ </Component>
20
23
  );
21
24
  };
@@ -0,0 +1,21 @@
1
+ .heading {
2
+ margin: 0;
3
+ }
4
+
5
+ .heading[data-size="sm"] {
6
+ font-size: 1.125rem;
7
+ font-weight: 500;
8
+ line-height: 1.25;
9
+ }
10
+
11
+ .heading[data-size="md"] {
12
+ font-size: 1.25rem;
13
+ font-weight: 500;
14
+ line-height: 1.5rem;
15
+ }
16
+
17
+ .heading[data-size="lg"] {
18
+ font-size: 1.5rem;
19
+ font-weight: 500;
20
+ line-height: 1.5rem;
21
+ }
@@ -1 +1,2 @@
1
1
  export * from './Typography';
2
+ export * from './Heading';
@@ -4,6 +4,14 @@
4
4
  line-height: 1.5;
5
5
  }
6
6
 
7
+ .item[data-size="sm"] {
8
+ font-size: 0.875rem;
9
+ }
10
+
11
+ .item[data-size="md"] {
12
+ font-size: 1rem;
13
+ }
14
+
7
15
  .typography[data-size="lg"] {
8
16
  font-size: 1.125rem;
9
17
  }
@@ -15,6 +15,8 @@ export * from './Layout';
15
15
  export * from './List';
16
16
  export * from './Menu';
17
17
  export * from './Meta';
18
+ export * from './Searchbar';
18
19
  export * from './Snackbar';
19
20
  export * from './Toolbar';
21
+ export * from './Page';
20
22
  export * from './Typography';
@@ -0,0 +1,3 @@
1
+ export * from './useClickOutside';
2
+ export * from './useEscapeKey';
3
+ export * from './useMenu';
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
  import { useEffect } from 'react';
3
3
 
4
- export const useEscapeKey = (onEscape: () => void): void => {
4
+ export const useEscapeKey = (onEscape?: () => void): void => {
5
5
  useEffect(() => {
6
6
  const handleEscape = (event: KeyboardEvent) => {
7
7
  if (event.key === 'Escape') {
8
- onEscape();
8
+ onEscape?.();
9
9
  }
10
10
  };
11
11
  document.addEventListener('keydown', handleEscape);
@@ -0,0 +1,80 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+
3
+ export interface UseMenuItemProps<T> {
4
+ menuIndex: number;
5
+ active?: boolean;
6
+ props: T;
7
+ }
8
+
9
+ export interface UseMenuGroup<T, V> {
10
+ items: UseMenuItemProps<T>[];
11
+ props: Record<string, V>;
12
+ }
13
+
14
+ export interface UseMenuOutput<T, V> {
15
+ menu: UseMenuGroup<T, V>[];
16
+ activeIndex: number;
17
+ setActiveIndex: (activeIndex: number) => void;
18
+ }
19
+
20
+ export interface UseMenuInput<T, V> {
21
+ items: T[];
22
+ groups: Record<string, V>;
23
+ groupByKey?: keyof T;
24
+ keyboardEvents?: boolean;
25
+ }
26
+
27
+ export const useMenu = <T, V>({
28
+ items,
29
+ groups,
30
+ groupByKey,
31
+ keyboardEvents = false,
32
+ }: UseMenuInput<T, V>): UseMenuOutput<T, V> => {
33
+ const [activeIndex, setActiveIndex] = useState<number>(-1);
34
+
35
+ const menu = useMemo(() => {
36
+ const flatItems: T[] = [];
37
+ const grouped = items.reduce(
38
+ (acc, item) => {
39
+ const key = groupByKey && item[groupByKey] ? (item[groupByKey] as string) : 'ungrouped';
40
+ acc[key] = acc[key] || [];
41
+ acc[key].push(item);
42
+ flatItems.push(item);
43
+ return acc;
44
+ },
45
+ {} as Record<string, T[]>,
46
+ );
47
+
48
+ return Object.entries(grouped).map(([key, groupItems]) => ({
49
+ items: groupItems.map((item) => ({
50
+ menuIndex: flatItems.indexOf(item),
51
+ active: activeIndex === flatItems.indexOf(item),
52
+ props: item,
53
+ })),
54
+ props: groups[key] || {},
55
+ }));
56
+ }, [items, groupByKey, activeIndex, groups]);
57
+
58
+ const handleKeyDown = useCallback(
59
+ (event: KeyboardEvent) => {
60
+ if (event.key === 'ArrowDown') {
61
+ setActiveIndex((prevIndex) => (prevIndex + 1) % items.length);
62
+ } else if (event.key === 'ArrowUp') {
63
+ setActiveIndex((prevIndex) => (prevIndex - 1 + items.length) % items.length);
64
+ }
65
+ },
66
+ [items.length],
67
+ );
68
+
69
+ useEffect(() => {
70
+ if (keyboardEvents) {
71
+ setActiveIndex(0);
72
+ document.addEventListener('keydown', handleKeyDown);
73
+ return () => {
74
+ document.removeEventListener('keydown', handleKeyDown);
75
+ };
76
+ }
77
+ }, [handleKeyDown, keyboardEvents]);
78
+
79
+ return { menu, activeIndex, setActiveIndex };
80
+ };
package/lib/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './components';
2
+ export * from './hooks';
@@ -0,0 +1,43 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { LayoutBase, type LayoutTheme, MenuBase, MenuItem, type MenuItemColor, MetaItem } from '../../components';
3
+
4
+ const meta = {
5
+ title: 'Demo/Color/MenuItem',
6
+ component: MenuItem,
7
+ tags: ['autodocs'],
8
+ parameters: {},
9
+ args: {
10
+ id: 'inbox',
11
+ },
12
+ } satisfies Meta<typeof MenuItem>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const ThemesAndColors = () => {
18
+ const themes: LayoutTheme[] = ['global', 'neutral', 'company', 'person', 'global-dark'];
19
+ const colors: MenuItemColor[] = ['neutral', 'subtle', 'strong', 'company', 'person'];
20
+
21
+ return (
22
+ <div style={{ display: 'flex', width: '100%' }}>
23
+ {themes.map((theme) => {
24
+ return (
25
+ <div key={theme} style={{ flexGrow: 1 }}>
26
+ <LayoutBase theme={theme}>
27
+ <MenuBase>
28
+ {colors.map((color) => {
29
+ return (
30
+ <div key={color}>
31
+ <MenuItem icon="inbox" title="Title" color={color} id="inbox" />
32
+ <MetaItem>{theme + '/' + color}</MetaItem>
33
+ </div>
34
+ );
35
+ })}
36
+ </MenuBase>
37
+ </LayoutBase>
38
+ </div>
39
+ );
40
+ })}
41
+ </div>
42
+ );
43
+ };
@@ -0,0 +1,19 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Swatches } from './Swatches';
3
+
4
+ const meta = {
5
+ title: 'Demo/Color/Swatches',
6
+ component: Swatches,
7
+ tags: ['autodocs'],
8
+ parameters: {
9
+ layout: 'fullscreen',
10
+ },
11
+ args: {},
12
+ } satisfies Meta<typeof Swatches>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = {
18
+ args: {},
19
+ };
@@ -0,0 +1,42 @@
1
+ import type { LayoutTheme } from '../../components';
2
+ import colors from './colors.json';
3
+ import styles from './swatches.module.css';
4
+
5
+ const themes = {
6
+ neutral: 'Neutral',
7
+ person: 'Person',
8
+ company: 'Company',
9
+ };
10
+
11
+ export interface SwatchesProps {
12
+ theme: LayoutTheme;
13
+ }
14
+
15
+ export const SwatchesList = ({ theme }: SwatchesProps) => {
16
+ return (
17
+ <div className={styles.list} data-theme={theme}>
18
+ {Object.keys(colors).map((key) => {
19
+ const style = {
20
+ backgroundColor: 'var(--theme-' + key + ')',
21
+ };
22
+
23
+ return (
24
+ <section className={styles.item}>
25
+ <div style={style} className={styles.swatch}></div>
26
+ {key}
27
+ </section>
28
+ );
29
+ })}
30
+ </div>
31
+ );
32
+ };
33
+
34
+ export const Swatches = ({ theme }: SwatchesProps) => {
35
+ return (
36
+ <div className={styles.row}>
37
+ {Object.keys(themes).map((key) => {
38
+ return <SwatchesList theme={key} />;
39
+ })}
40
+ </div>
41
+ );
42
+ };
@@ -0,0 +1,62 @@
1
+ {
2
+ "background-default": {
3
+ "$type": "color",
4
+ "$value": "{color.<color>.1}"
5
+ },
6
+ "background-subtle": {
7
+ "$type": "color",
8
+ "$value": "{color.<color>.2}"
9
+ },
10
+ "surface-default": {
11
+ "$type": "color",
12
+ "$value": "{color.<color>.3}"
13
+ },
14
+ "surface-hover": {
15
+ "$type": "color",
16
+ "$value": "{color.<color>.4}"
17
+ },
18
+ "surface-active": {
19
+ "$type": "color",
20
+ "$value": "{color.<color>.5}"
21
+ },
22
+ "border-subtle": {
23
+ "$type": "color",
24
+ "$value": "{color.<color>.6}"
25
+ },
26
+ "border-default": {
27
+ "$type": "color",
28
+ "$value": "{color.<color>.7}"
29
+ },
30
+ "border-strong": {
31
+ "$type": "color",
32
+ "$value": "{color.<color>.8}"
33
+ },
34
+ "base-default": {
35
+ "$type": "color",
36
+ "$value": "{color.<color>.9}"
37
+ },
38
+ "base-hover": {
39
+ "$type": "color",
40
+ "$value": "{color.<color>.10}"
41
+ },
42
+ "base-active": {
43
+ "$type": "color",
44
+ "$value": "{color.<color>.11}"
45
+ },
46
+ "text-subtle": {
47
+ "$type": "color",
48
+ "$value": "{color.<color>.12}"
49
+ },
50
+ "text-default": {
51
+ "$type": "color",
52
+ "$value": "{color.<color>.13}"
53
+ },
54
+ "contrast-default": {
55
+ "$type": "color",
56
+ "$value": "{color.<color>.contrast-1}"
57
+ },
58
+ "contrast-subtle": {
59
+ "$type": "color",
60
+ "$value": "{color.<color>.contrast-2}"
61
+ }
62
+ }
@@ -0,0 +1,14 @@
1
+ .swatch {
2
+ width: 4rem;
3
+ height: 4rem;
4
+ border: 1px solid;
5
+ }
6
+
7
+ .row {
8
+ display: flex;
9
+ }
10
+
11
+ .col {
12
+ display: flex;
13
+ flex-direction: column;
14
+ }
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+ import {
3
+ Heading,
4
+ ListBase,
5
+ ListItem,
6
+ MetaItem,
7
+ PageBase,
8
+ SectionBase,
9
+ SectionFooter,
10
+ SectionHeader,
11
+ Typography,
12
+ } from '../../components';
13
+ import { InboxToolbar } from './InboxToolbar';
14
+
15
+ export function BookmarksPage() {
16
+ const bookmarks = [
17
+ {
18
+ id: '1',
19
+ title: '123',
20
+ },
21
+ {
22
+ id: '2',
23
+ title: '123',
24
+ },
25
+ {
26
+ id: '3',
27
+ title: '123',
28
+ },
29
+ ];
30
+
31
+ const count = bookmarks.length;
32
+ const title = (count > 1 && count + ' lagrede søk') || (count && '1 lagret søk') || 'Ingen lagrede søk';
33
+
34
+ return (
35
+ <PageBase margin="lg" spacing="lg">
36
+ <InboxToolbar />
37
+ <SectionBase spacing="lg">
38
+ <SectionHeader padding>
39
+ <Heading size="md">{title}</Heading>
40
+ </SectionHeader>
41
+ <ListBase spacing="sm">
42
+ {bookmarks.map((item) => (
43
+ <ListItem {...item} key={item.id} />
44
+ ))}
45
+ </ListBase>
46
+ <SectionFooter padding>
47
+ <MetaItem>Sist oppdatert: 10 minutter siden.</MetaItem>
48
+ </SectionFooter>
49
+ </SectionBase>
50
+ </PageBase>
51
+ );
52
+ }
@@ -0,0 +1,15 @@
1
+ import { ActionMenu, Dialog, PageBase } from '../../components';
2
+ import type { DialogProps } from '../../components';
3
+
4
+ interface DialogPageProps {
5
+ dialog: DialogProps;
6
+ }
7
+
8
+ export function DialogPage({ dialog }: DialogPageProps) {
9
+ return (
10
+ <PageBase inset>
11
+ <Dialog {...dialog} />
12
+ <ActionMenu items={dialog?.menu?.items} />
13
+ </PageBase>
14
+ );
15
+ }
@@ -0,0 +1,55 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { accounts, dialogs } from './';
3
+ import { Inbox } from './Inbox';
4
+
5
+ const meta = {
6
+ title: 'Demo/Inbox/Inbox',
7
+ component: Inbox,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'fullscreen',
11
+ },
12
+ args: {
13
+ inboxId: 'inbox',
14
+ accounts,
15
+ accountId: 'a0',
16
+ dialogs,
17
+ },
18
+ } satisfies Meta<typeof Inbox>;
19
+
20
+ export default meta;
21
+ type Story = StoryObj<typeof meta>;
22
+
23
+ export const Default: Story = {
24
+ args: {},
25
+ };
26
+
27
+ export const BulkMode: Story = {
28
+ args: {
29
+ selectedIds: ['d1'],
30
+ },
31
+ };
32
+
33
+ export const DialogOpen: Story = {
34
+ args: {
35
+ dialogId: 'd1',
36
+ },
37
+ };
38
+
39
+ export const SavedSearches: Story = {
40
+ args: {
41
+ inboxId: 'bookmarks',
42
+ },
43
+ };
44
+
45
+ export const ProfilePage: Story = {
46
+ args: {
47
+ inboxId: 'profile',
48
+ },
49
+ };
50
+
51
+ export const SettingsPage: Story = {
52
+ args: {
53
+ inboxId: 'settings',
54
+ },
55
+ };