@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.
- package/.storybook/StoryDecorator.tsx +1 -1
- package/.storybook/main.ts +3 -4
- package/.storybook/preview.tsx +28 -0
- package/CHANGELOG.md +14 -0
- package/biome.jsonc +1 -1
- package/lib/components/Attachment/AttachmentLink.stories.ts +1 -1
- package/lib/components/Attachment/AttachmentList.stories.ts +1 -1
- package/lib/components/Button/Button.tsx +13 -10
- package/lib/components/Button/ButtonBase.tsx +1 -1
- package/lib/components/Button/ButtonIcon.tsx +16 -0
- package/lib/components/Button/ButtonLabel.tsx +18 -0
- package/lib/components/Button/Buttons.stories.tsx +64 -0
- package/lib/components/Button/ComboButton.tsx +9 -7
- package/lib/components/Button/IconButton.stories.tsx +47 -0
- package/lib/components/Button/IconButton.tsx +15 -5
- package/lib/components/Button/button.module.css +5 -46
- package/lib/components/Button/buttonBase.module.css +55 -23
- package/lib/components/Button/buttonIcon.module.css +17 -0
- package/lib/components/Button/buttonLabel.module.css +17 -0
- package/lib/components/Button/comboButton.module.css +15 -65
- package/lib/components/Button/iconButton.module.css +21 -4
- package/lib/components/Button/index.ts +2 -0
- package/lib/components/ContextMenu/ContextMenu.stories.ts +49 -0
- package/lib/components/ContextMenu/ContextMenu.tsx +12 -20
- package/lib/components/ContextMenu/ContextMenuBase.tsx +33 -0
- package/lib/components/Dialog/Dialog.stories.ts +12 -5
- package/lib/components/Dialog/Dialog.tsx +2 -0
- package/lib/components/Dialog/DialogGroup.tsx +24 -0
- package/lib/components/Dialog/DialogList.stories.ts +14 -10
- package/lib/components/Dialog/DialogList.tsx +26 -11
- package/lib/components/Dialog/DialogListItem.tsx +12 -2
- package/lib/components/Dialog/DialogListItemBase.tsx +4 -2
- package/lib/components/Dialog/DialogNav.stories.ts +5 -5
- package/lib/components/Dialog/DialogNav.tsx +2 -6
- package/lib/components/Dialog/DialogSelect.tsx +1 -1
- package/lib/components/Dialog/dialogGroup.module.css +35 -0
- package/lib/components/Dropdown/Backdrop.tsx +4 -3
- package/lib/components/Dropdown/DrawerBase.tsx +5 -2
- package/lib/components/Dropdown/DrawerBody.tsx +12 -0
- package/lib/components/Dropdown/DrawerButton.tsx +17 -0
- package/lib/components/Dropdown/DrawerFooter.tsx +12 -0
- package/lib/components/Dropdown/DrawerHeader.tsx +19 -0
- package/lib/components/Dropdown/DrawerOrDropdown.tsx +29 -0
- package/lib/components/Dropdown/DropdownBase.tsx +17 -2
- package/lib/components/Dropdown/backdrop.module.css +3 -0
- package/lib/components/Dropdown/drawerBase.module.css +9 -0
- package/lib/components/Dropdown/drawerBody.module.css +5 -0
- package/lib/components/Dropdown/drawerButton.module.css +6 -0
- package/lib/components/Dropdown/drawerFooter.module.css +13 -0
- package/lib/components/Dropdown/drawerHeader.module.css +17 -0
- package/lib/components/Dropdown/drawerOrDropdown.module.css +19 -0
- package/lib/components/Dropdown/dropdownBase.module.css +20 -4
- package/lib/components/Dropdown/index.ts +7 -1
- package/lib/components/Footer/footerMenu.module.css +5 -0
- package/lib/components/GlobalMenu/AccountButton.tsx +29 -0
- package/lib/components/GlobalMenu/AccountMenu.stories.tsx +65 -0
- package/lib/components/GlobalMenu/AccountMenu.tsx +73 -0
- package/lib/components/GlobalMenu/BackButton.tsx +10 -0
- package/lib/components/GlobalMenu/GlobalMenu.stories.tsx +112 -121
- package/lib/components/GlobalMenu/GlobalMenu.tsx +41 -89
- package/lib/components/GlobalMenu/GlobalMenuBase.tsx +22 -0
- package/lib/components/GlobalMenu/LogoutButton.tsx +19 -0
- package/lib/components/GlobalMenu/globalMenuBase.module.css +39 -0
- package/lib/components/GlobalMenu/index.tsx +1 -1
- package/lib/components/GlobalMenu/logoutButton.module.css +9 -0
- package/lib/components/Header/{Header.stories.ts → Header.stories.tsx} +79 -20
- package/lib/components/Header/Header.tsx +25 -38
- package/lib/components/Header/HeaderBase.tsx +7 -3
- package/lib/components/Header/header.module.css +10 -42
- package/lib/components/Header/headerBase.module.css +43 -0
- package/lib/components/Header/headerButton.module.css +1 -0
- package/lib/components/Layout/Layout.stories.tsx +77 -38
- package/lib/components/Layout/Layout.tsx +5 -3
- package/lib/components/Layout/LayoutBase.tsx +3 -2
- package/lib/components/Layout/layoutBase.module.css +11 -0
- package/lib/components/Layout/layoutBody.module.css +1 -0
- package/lib/components/LayoutAction/ActionHeader.tsx +1 -1
- package/lib/components/LayoutAction/ActionMenu.tsx +2 -4
- package/lib/components/LayoutAction/actionMenu.module.css +3 -0
- package/lib/components/List/List.stories.tsx +43 -0
- package/lib/components/List/List.tsx +6 -6
- package/lib/components/List/ListBase.tsx +6 -6
- package/lib/components/List/ListItem.tsx +4 -1
- package/lib/components/List/ListItemBase.tsx +20 -4
- package/lib/components/List/listBase.module.css +3 -3
- package/lib/components/List/listItemBase.module.css +4 -0
- package/lib/components/Menu/Menu.stories.ts +46 -46
- package/lib/components/Menu/Menu.tsx +3 -102
- package/lib/components/Menu/MenuBase.tsx +47 -3
- package/lib/components/Menu/MenuItem.tsx +8 -4
- package/lib/components/Menu/MenuItemBase.tsx +15 -2
- package/lib/components/Menu/MenuItems.stories.ts +438 -0
- package/lib/components/Menu/MenuItems.tsx +96 -0
- package/lib/components/Menu/MenuOption.tsx +4 -1
- package/lib/components/Menu/index.ts +1 -1
- package/lib/components/Menu/menu.module.css +2 -3
- package/lib/components/Menu/menuBase.module.css +25 -0
- package/lib/components/Menu/menuItemBase.module.css +11 -5
- package/lib/components/Menu/menuItemLabel.module.css +11 -1
- package/lib/components/Menu/menuSearch.module.css +1 -0
- package/lib/components/Meta/MetaItemBase.tsx +1 -1
- package/lib/components/Meta/MetaItemLabel.tsx +1 -1
- package/lib/components/Meta/MetaItemMedia.tsx +1 -1
- package/lib/components/Page/PageBase.tsx +14 -0
- package/lib/components/Page/PageHeader.tsx +21 -0
- package/lib/components/Page/PageHeaderMedia.tsx +25 -0
- package/lib/components/Page/SectionBase.tsx +52 -0
- package/lib/components/Page/SectionFooter.tsx +15 -0
- package/lib/components/Page/SectionHeader.tsx +16 -0
- package/lib/components/Page/index.ts +5 -0
- package/lib/components/Page/pageHeader.module.css +5 -0
- package/lib/components/Page/sectionBase.module.css +82 -0
- package/lib/components/Page/sectionFooter.module.css +8 -0
- package/lib/components/Page/sectionHeader.module.css +9 -0
- package/lib/components/RootProvider/RootProvider.tsx +43 -7
- package/lib/components/Searchbar/Autocomplete.stories.tsx +77 -0
- package/lib/components/Searchbar/Autocomplete.tsx +44 -0
- package/lib/components/Searchbar/AutocompleteBase.tsx +16 -0
- package/lib/components/Searchbar/AutocompleteGroup.tsx +17 -0
- package/lib/components/Searchbar/AutocompleteItem.tsx +23 -0
- package/lib/components/Searchbar/SearchField.tsx +78 -0
- package/lib/components/Searchbar/Searchbar.stories.tsx +151 -0
- package/lib/components/Searchbar/Searchbar.tsx +18 -0
- package/lib/components/Searchbar/SearchbarBase.tsx +23 -0
- package/lib/components/Searchbar/autocompleteBase.module.css +17 -0
- package/lib/components/Searchbar/autocompleteGroup.module.css +3 -0
- package/lib/components/Searchbar/autocompleteItem.module.css +19 -0
- package/lib/components/Searchbar/index.ts +1 -0
- package/lib/components/Searchbar/searchField.module.css +54 -0
- package/lib/components/Searchbar/searchbarBase.module.css +20 -0
- package/lib/components/Toolbar/Toolbar.stories.tsx +10 -10
- package/lib/components/Toolbar/Toolbar.tsx +28 -10
- package/lib/components/Toolbar/ToolbarAdd.tsx +6 -5
- package/lib/components/Toolbar/ToolbarBase.tsx +5 -20
- package/lib/components/Toolbar/ToolbarButton.tsx +1 -1
- package/lib/components/Toolbar/ToolbarFilter.tsx +11 -6
- package/lib/components/Toolbar/ToolbarMenu.tsx +8 -7
- package/lib/components/Toolbar/ToolbarOptions.stories.ts +5 -5
- package/lib/components/Toolbar/ToolbarOptions.tsx +34 -21
- package/lib/components/Toolbar/toolbarAdd.module.css +7 -0
- package/lib/components/Toolbar/toolbarBase.module.css +19 -0
- package/lib/components/Toolbar/toolbarButton.module.css +1 -1
- package/lib/components/Toolbar/toolbarFilter.module.css +25 -0
- package/lib/components/Toolbar/toolbarMenu.module.css +7 -0
- package/lib/components/Typography/Heading.tsx +23 -0
- package/lib/components/Typography/Typography.tsx +8 -5
- package/lib/components/Typography/heading.module.css +21 -0
- package/lib/components/Typography/index.ts +1 -0
- package/lib/components/Typography/typography.module.css +8 -0
- package/lib/components/index.ts +2 -0
- package/lib/hooks/index.ts +3 -0
- package/lib/{components/Menu → hooks}/useEscapeKey.ts +2 -2
- package/lib/hooks/useMenu.tsx +80 -0
- package/lib/index.ts +1 -0
- package/lib/stories/Color/MenuItem.stories.tsx +43 -0
- package/lib/stories/Color/Swatches.stories.tsx +19 -0
- package/lib/stories/Color/Swatches.tsx +42 -0
- package/lib/stories/Color/colors.json +62 -0
- package/lib/stories/Color/swatches.module.css +14 -0
- package/lib/stories/Inbox/BookmarksPage.tsx +52 -0
- package/lib/stories/Inbox/DialogPage.tsx +15 -0
- package/lib/stories/Inbox/Inbox.stories.tsx +55 -0
- package/lib/stories/Inbox/Inbox.tsx +12 -0
- package/lib/stories/Inbox/InboxLayout.tsx +50 -0
- package/lib/stories/Inbox/InboxPage.tsx +50 -0
- package/lib/stories/Inbox/InboxProvider.tsx +136 -0
- package/lib/stories/Inbox/InboxSection.tsx +39 -0
- package/lib/stories/Inbox/InboxToolbar.tsx +94 -0
- package/lib/stories/Inbox/ProfilePage.tsx +35 -0
- package/lib/stories/Inbox/SettingsPage.tsx +19 -0
- package/lib/stories/Inbox/accounts/accounts.ts +24 -0
- package/lib/stories/Inbox/accounts/index.ts +1 -0
- package/lib/stories/Inbox/actionMenu.ts +24 -0
- package/lib/stories/Inbox/dialogs/brreg-completed.json +35 -0
- package/lib/stories/Inbox/dialogs/brreg-draft.json +45 -0
- package/lib/stories/Inbox/dialogs/index.ts +10 -0
- package/lib/stories/Inbox/dialogs/skatt-2023.json +33 -0
- package/lib/stories/Inbox/groupBy.ts +19 -0
- package/lib/stories/Inbox/inboxSection.module.css +19 -0
- package/lib/stories/Inbox/index.ts +15 -0
- package/lib/stories/Inbox/layout/footer.ts +27 -0
- package/lib/stories/Inbox/layout/header.ts +11 -0
- package/lib/stories/Inbox/layout/index.ts +3 -0
- package/lib/stories/Inbox/layout/menu.ts +64 -0
- package/package.json +15 -13
- package/tsconfig.json +7 -2
- package/lib/components/Header/HeaderSearch.stories.ts +0 -20
- package/lib/components/Header/HeaderSearch.tsx +0 -44
- package/lib/components/Header/headerSearch.module.css +0 -30
- package/lib/components/Menu/MenuGroup.tsx +0 -18
- package/lib/components/Menu/__menuItem.module.css +0 -130
- package/lib/components/Toolbar/toolbar.module.css +0 -43
- /package/lib/components/ContextMenu/{contextMenu.module.css → contextMenuBase.module.css} +0 -0
- /package/lib/{components/Menu → hooks}/useClickOutside.ts +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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,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
|
+
}
|
|
@@ -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,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 = '
|
|
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
|
-
<
|
|
20
|
+
<Component className={cx(styles.typography, className)} data-size={size} data-theme={theme}>
|
|
18
21
|
{children}
|
|
19
|
-
</
|
|
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
|
+
}
|
package/lib/components/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
3
|
|
|
4
|
-
export const useEscapeKey = (onEscape
|
|
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
|
@@ -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,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
|
+
};
|