@altinn/altinn-components 0.0.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 (126) hide show
  1. package/.github/workflows/ci-cd-main.yml +44 -0
  2. package/.github/workflows/ci-cd-pull-request.yml +39 -0
  3. package/.node-version +1 -0
  4. package/.storybook/main.ts +22 -0
  5. package/.storybook/preview.ts +15 -0
  6. package/CHANGELOG.md +13 -0
  7. package/README.md +2 -0
  8. package/biome.jsonc +65 -0
  9. package/lib/components/Avatar/Avatar.tsx +91 -0
  10. package/lib/components/Avatar/AvatarGroup.stories.ts +67 -0
  11. package/lib/components/Avatar/AvatarGroup.tsx +42 -0
  12. package/lib/components/Avatar/avatar.module.css +59 -0
  13. package/lib/components/Avatar/avatar.stories.tsx +44 -0
  14. package/lib/components/Avatar/avatarGroup.module.css +78 -0
  15. package/lib/components/Avatar/color.ts +71 -0
  16. package/lib/components/Avatar/index.ts +2 -0
  17. package/lib/components/Badge/Badge.tsx +19 -0
  18. package/lib/components/Badge/badge.module.css +36 -0
  19. package/lib/components/Badge/index.tsx +1 -0
  20. package/lib/components/Button/Button.stories.ts +44 -0
  21. package/lib/components/Button/Button.tsx +39 -0
  22. package/lib/components/Button/ButtonBase.tsx +53 -0
  23. package/lib/components/Button/ComboButton.stories.ts +45 -0
  24. package/lib/components/Button/ComboButton.tsx +44 -0
  25. package/lib/components/Button/button.module.css +82 -0
  26. package/lib/components/Button/buttonBase.module.css +77 -0
  27. package/lib/components/Button/comboButton.module.css +83 -0
  28. package/lib/components/Button/index.ts +3 -0
  29. package/lib/components/Header/DigdirLogomark.tsx +23 -0
  30. package/lib/components/Header/GlobalMenu.stories.tsx +202 -0
  31. package/lib/components/Header/GlobalMenu.tsx +131 -0
  32. package/lib/components/Header/Header.stories.ts +85 -0
  33. package/lib/components/Header/Header.tsx +64 -0
  34. package/lib/components/Header/HeaderBase.tsx +10 -0
  35. package/lib/components/Header/HeaderButton.stories.ts +54 -0
  36. package/lib/components/Header/HeaderButton.tsx +55 -0
  37. package/lib/components/Header/HeaderLogo.stories.ts +17 -0
  38. package/lib/components/Header/HeaderLogo.tsx +22 -0
  39. package/lib/components/Header/HeaderSearch.stories.ts +20 -0
  40. package/lib/components/Header/HeaderSearch.tsx +44 -0
  41. package/lib/components/Header/globalMenu.module.css +28 -0
  42. package/lib/components/Header/header.module.css +39 -0
  43. package/lib/components/Header/headerButton.module.css +35 -0
  44. package/lib/components/Header/headerLogo.module.css +24 -0
  45. package/lib/components/Header/headerSearch.module.css +30 -0
  46. package/lib/components/Header/index.tsx +5 -0
  47. package/lib/components/Icon/CheckboxIcon.stories.ts +25 -0
  48. package/lib/components/Icon/CheckboxIcon.tsx +29 -0
  49. package/lib/components/Icon/Icon.stories.ts +24 -0
  50. package/lib/components/Icon/Icon.tsx +23 -0
  51. package/lib/components/Icon/RadioIcon.stories.ts +25 -0
  52. package/lib/components/Icon/RadioIcon.tsx +29 -0
  53. package/lib/components/Icon/SvgIcon.tsx +18 -0
  54. package/lib/components/Icon/__AkselIcon.tsx +37 -0
  55. package/lib/components/Icon/checkboxIcon.module.css +21 -0
  56. package/lib/components/Icon/icon.module.css +4 -0
  57. package/lib/components/Icon/iconsMap.tsx +2078 -0
  58. package/lib/components/Icon/index.ts +5 -0
  59. package/lib/components/Icon/radioIcon.module.css +21 -0
  60. package/lib/components/Layout/Layout.stories.ts +127 -0
  61. package/lib/components/Layout/Layout.tsx +40 -0
  62. package/lib/components/Layout/LayoutBase.stories.ts +17 -0
  63. package/lib/components/Layout/LayoutBase.tsx +30 -0
  64. package/lib/components/Layout/LayoutBody.stories.ts +17 -0
  65. package/lib/components/Layout/LayoutBody.tsx +16 -0
  66. package/lib/components/Layout/LayoutContent.stories.ts +17 -0
  67. package/lib/components/Layout/LayoutContent.tsx +15 -0
  68. package/lib/components/Layout/LayoutSidebar.stories.ts +17 -0
  69. package/lib/components/Layout/LayoutSidebar.tsx +16 -0
  70. package/lib/components/Layout/index.tsx +4 -0
  71. package/lib/components/Layout/layout.module.css +63 -0
  72. package/lib/components/Menu/Menu.stories.ts +495 -0
  73. package/lib/components/Menu/Menu.tsx +123 -0
  74. package/lib/components/Menu/MenuBase.tsx +17 -0
  75. package/lib/components/Menu/MenuGroup.tsx +18 -0
  76. package/lib/components/Menu/MenuHeader.tsx +13 -0
  77. package/lib/components/Menu/MenuItem.stories.ts +127 -0
  78. package/lib/components/Menu/MenuItem.tsx +58 -0
  79. package/lib/components/Menu/MenuItemBase.tsx +62 -0
  80. package/lib/components/Menu/MenuItemLabel.tsx +30 -0
  81. package/lib/components/Menu/MenuItemMedia.tsx +42 -0
  82. package/lib/components/Menu/MenuOption.stories.ts +50 -0
  83. package/lib/components/Menu/MenuOption.tsx +45 -0
  84. package/lib/components/Menu/MenuSearch.stories.ts +18 -0
  85. package/lib/components/Menu/MenuSearch.tsx +25 -0
  86. package/lib/components/Menu/index.ts +10 -0
  87. package/lib/components/Menu/menu.module.css +26 -0
  88. package/lib/components/Menu/menuHeader.module.css +12 -0
  89. package/lib/components/Menu/menuItem.module.css +136 -0
  90. package/lib/components/Menu/menuOption.module.css +29 -0
  91. package/lib/components/Menu/menuSearch.module.css +29 -0
  92. package/lib/components/Menu/useClickOutside.ts +21 -0
  93. package/lib/components/Menu/useEscapeKey.ts +16 -0
  94. package/lib/components/Toolbar/Toolbar.stories.tsx +188 -0
  95. package/lib/components/Toolbar/Toolbar.tsx +138 -0
  96. package/lib/components/Toolbar/ToolbarAdd.stories.ts +25 -0
  97. package/lib/components/Toolbar/ToolbarAdd.tsx +25 -0
  98. package/lib/components/Toolbar/ToolbarBase.tsx +27 -0
  99. package/lib/components/Toolbar/ToolbarButton.stories.ts +32 -0
  100. package/lib/components/Toolbar/ToolbarButton.tsx +65 -0
  101. package/lib/components/Toolbar/ToolbarFilter.stories.ts +66 -0
  102. package/lib/components/Toolbar/ToolbarFilter.tsx +70 -0
  103. package/lib/components/Toolbar/ToolbarMenu.stories.ts +37 -0
  104. package/lib/components/Toolbar/ToolbarMenu.tsx +28 -0
  105. package/lib/components/Toolbar/ToolbarOptions.stories.ts +108 -0
  106. package/lib/components/Toolbar/ToolbarOptions.tsx +61 -0
  107. package/lib/components/Toolbar/ToolbarSearch.stories.ts +19 -0
  108. package/lib/components/Toolbar/ToolbarSearch.tsx +24 -0
  109. package/lib/components/Toolbar/index.js +3 -0
  110. package/lib/components/Toolbar/toolbar.module.css +43 -0
  111. package/lib/components/Toolbar/toolbarButton.module.css +3 -0
  112. package/lib/components/Toolbar/toolbarSearch.module.css +28 -0
  113. package/lib/components/index.ts +1 -0
  114. package/lib/css/colors.css +113 -0
  115. package/lib/css/global.css +12 -0
  116. package/lib/css/theme-company.css +15 -0
  117. package/lib/css/theme-global.css +15 -0
  118. package/lib/css/theme-neutral.css +15 -0
  119. package/lib/css/theme-person.css +15 -0
  120. package/lib/css/theme.css +24 -0
  121. package/lib/index.ts +1 -0
  122. package/package.json +52 -0
  123. package/tsconfig.json +23 -0
  124. package/tsconfig.node.json +11 -0
  125. package/typings.d.ts +1 -0
  126. package/vite.config.ts +20 -0
@@ -0,0 +1,202 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState } from 'react';
3
+ import { GlobalMenu } from './GlobalMenu';
4
+
5
+ const meta = {
6
+ title: 'Header/GlobalMenu',
7
+ component: GlobalMenu,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ args: {
13
+ groups: {
14
+ apps: {
15
+ divider: true,
16
+ },
17
+ },
18
+ items: [
19
+ {
20
+ id: 'inbox',
21
+ group: 'apps',
22
+ size: 'lg',
23
+ icon: 'inbox',
24
+ label: 'Innboks',
25
+ },
26
+ {
27
+ id: 'settings',
28
+ group: 'apps',
29
+ size: 'lg',
30
+ icon: 'cog',
31
+ label: 'Settings',
32
+ },
33
+ ],
34
+ },
35
+ } satisfies Meta<typeof GlobalMenu>;
36
+
37
+ export default meta;
38
+ type Story = StoryObj<typeof meta>;
39
+
40
+ export const Default: Story = {};
41
+
42
+ export const Login: Story = {
43
+ args: {
44
+ expanded: true,
45
+ items: [
46
+ {
47
+ id: 'login',
48
+ size: 'lg',
49
+ icon: 'padlock-locked',
50
+ title: 'Logg inn',
51
+ },
52
+ {
53
+ id: 'help',
54
+ size: 'lg',
55
+ icon: 'chat-exclamationmark',
56
+ title: 'Hjelp',
57
+ },
58
+ ],
59
+ },
60
+ };
61
+
62
+ export const ControlledStateLogin = () => {
63
+ const [expanded, setExpanded] = useState<boolean>(false);
64
+ return (
65
+ <GlobalMenu
66
+ {...Login.args}
67
+ expanded={expanded}
68
+ onToggle={() => setExpanded((prevState) => !prevState)}
69
+ items={Login.args.items ?? []}
70
+ />
71
+ );
72
+ };
73
+
74
+ export const Person: Story = {
75
+ args: {
76
+ accounts: [
77
+ {
78
+ type: 'person',
79
+ name: 'Aurora Mikalsen',
80
+ selected: true,
81
+ },
82
+ ],
83
+ },
84
+ };
85
+
86
+ export const Company: Story = {
87
+ args: {
88
+ accounts: [
89
+ {
90
+ type: 'company',
91
+ name: 'Bergen bar',
92
+ selected: true,
93
+ },
94
+ ],
95
+ },
96
+ };
97
+
98
+ export const Expanded: Story = {
99
+ args: {
100
+ expanded: true,
101
+ accounts: [
102
+ {
103
+ type: 'company',
104
+ name: 'Bergen bar',
105
+ selected: true,
106
+ },
107
+ ],
108
+ },
109
+ };
110
+
111
+ export const CustomLabel: Story = {
112
+ args: {
113
+ menuLabel: 'Meny',
114
+ accounts: [
115
+ {
116
+ type: 'person',
117
+ name: 'Aurora Mikalsen',
118
+ selected: true,
119
+ },
120
+ ],
121
+ },
122
+ };
123
+
124
+ export const Accounts: Story = {
125
+ args: {
126
+ menuLabel: 'Meny',
127
+ backLabel: 'Tilbake',
128
+ accountSearch: {
129
+ placeholder: 'Søk etter konto',
130
+ getResultsLabel: (hits = 0) => {
131
+ if (hits) {
132
+ return hits + ' treff';
133
+ }
134
+ return 'Ingen treff';
135
+ },
136
+ hidden: false,
137
+ },
138
+ expanded: true,
139
+ accountGroups: {
140
+ primary: {
141
+ title: 'Deg selv og favoritter',
142
+ },
143
+ secondary: {
144
+ divider: true,
145
+ title: 'Andre kontoer',
146
+ },
147
+ favourites: {
148
+ divider: true,
149
+ },
150
+ company: {
151
+ divider: true,
152
+ },
153
+ person: {
154
+ divider: true,
155
+ },
156
+ },
157
+ accounts: [
158
+ {
159
+ group: 'primary',
160
+ type: 'person',
161
+ name: 'Aurora Mikalsen',
162
+ selected: true,
163
+ },
164
+ {
165
+ group: 'favourites',
166
+ type: 'person',
167
+ name: 'Rakel Engelsvik',
168
+ selected: false,
169
+ },
170
+ {
171
+ group: 'favourites',
172
+ type: 'company',
173
+ name: 'Auroras keeperskole',
174
+ selected: false,
175
+ },
176
+ {
177
+ group: 'secondary',
178
+ type: 'company',
179
+ name: 'Keeperhansker AS',
180
+ selected: false,
181
+ },
182
+ {
183
+ group: 'secondary',
184
+ type: 'company',
185
+ name: 'Stadion drift AS',
186
+ selected: false,
187
+ },
188
+ {
189
+ group: 'secondary',
190
+ type: 'company',
191
+ name: 'Sportsklubben Brann',
192
+ selected: false,
193
+ },
194
+ {
195
+ group: 'secondary',
196
+ type: 'company',
197
+ name: 'Landslaget',
198
+ selected: false,
199
+ },
200
+ ],
201
+ },
202
+ };
@@ -0,0 +1,131 @@
1
+ 'use client';
2
+ import cx from 'classnames';
3
+ import { type MouseEventHandler, useState } from 'react';
4
+ import type { AvatarType } from '../Avatar';
5
+ import { Menu, type MenuGroups, MenuItem, type MenuItemProps, type MenuSearchProps } from '../Menu';
6
+ import { HeaderButton } from './HeaderButton';
7
+ import styles from './globalMenu.module.css';
8
+
9
+ export type Account = {
10
+ type: AvatarType;
11
+ name: string;
12
+ selected?: boolean;
13
+ group?: string;
14
+ };
15
+
16
+ export interface AccountSearch extends MenuSearchProps {
17
+ getResultsLabel?: (hits: number) => string;
18
+ hidden?: boolean;
19
+ }
20
+
21
+ export interface GlobalMenuProps {
22
+ expanded: boolean;
23
+ onToggle: MouseEventHandler;
24
+ items: MenuItemProps[];
25
+ groups?: MenuGroups;
26
+ accounts?: Account[];
27
+ accountGroups?: MenuGroups;
28
+ accountSearch?: AccountSearch;
29
+ menuLabel?: string;
30
+ backLabel?: string;
31
+ className?: string;
32
+ }
33
+
34
+ const defaultResultLabel = (hits: number) => `${hits} hits`;
35
+
36
+ export const GlobalMenu = ({
37
+ className,
38
+ expanded,
39
+ onToggle,
40
+ accounts = [],
41
+ accountGroups = {},
42
+ accountSearch,
43
+ items = [],
44
+ groups,
45
+ menuLabel = 'Menu',
46
+ backLabel = 'Back',
47
+ }: GlobalMenuProps) => {
48
+ const accountMenu: MenuItemProps[] = accounts.map((account) => ({
49
+ id: account.name,
50
+ group: account.group || 'search',
51
+ selected: account.selected,
52
+ title: account.name,
53
+ avatar: {
54
+ type: account.type,
55
+ name: account.name,
56
+ },
57
+ }));
58
+
59
+ const selectedAccount = accountMenu.find((account) => account.selected);
60
+ const [selectAccount, setSelectAccount] = useState<boolean>(false);
61
+ const [filterString, setFilterString] = useState<string>('');
62
+
63
+ const onToggleAccounts = () => {
64
+ setSelectAccount((prevState) => !prevState);
65
+ };
66
+
67
+ const accountMenuItem: MenuItemProps = {
68
+ ...selectedAccount,
69
+ id: 'account',
70
+ selected: false,
71
+ size: 'lg',
72
+ onClick: onToggleAccounts,
73
+ };
74
+
75
+ const globalMenu = selectedAccount ? [accountMenuItem, ...items] : items;
76
+
77
+ const filteredAccountMenu = filterString
78
+ ? accountMenu
79
+ .filter((item) => item?.title?.toLowerCase().includes(filterString.toLowerCase()))
80
+ .map((item) => {
81
+ return {
82
+ ...item,
83
+ group: 'search',
84
+ };
85
+ })
86
+ : accountMenu;
87
+
88
+ const filterAccountGroups = filterString
89
+ ? {
90
+ search: {
91
+ title:
92
+ accountSearch?.getResultsLabel?.(filteredAccountMenu.length) ??
93
+ defaultResultLabel(filteredAccountMenu.length),
94
+ },
95
+ }
96
+ : accountGroups;
97
+
98
+ const accountSearchItem: MenuSearchProps = {
99
+ name: 'account-search',
100
+ value: filterString,
101
+ placeholder: accountSearch?.placeholder ?? 'Find account',
102
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) => setFilterString(event.target.value),
103
+ };
104
+
105
+ const backItem: MenuItemProps = {
106
+ id: 'back',
107
+ title: backLabel ?? 'Back',
108
+ icon: 'arrow-left',
109
+ onClick: onToggleAccounts,
110
+ };
111
+
112
+ const accountSwitcher: MenuItemProps[] = [
113
+ ...(filteredAccountMenu.length > 0 ? filteredAccountMenu : [{ id: 'search', group: 'search', hidden: true }]),
114
+ ];
115
+
116
+ return (
117
+ <div className={cx(styles.button, className)}>
118
+ <HeaderButton as="div" avatar={accountMenuItem.avatar} onClick={onToggle} expanded={expanded} label={menuLabel} />
119
+ <div className={styles?.dropdown} aria-expanded={expanded}>
120
+ {selectAccount ? (
121
+ <>
122
+ <MenuItem {...backItem} />
123
+ <Menu theme="global" search={accountSearchItem} groups={filterAccountGroups} items={accountSwitcher} />
124
+ </>
125
+ ) : (
126
+ <Menu theme="global" groups={groups} items={globalMenu} />
127
+ )}
128
+ </div>
129
+ </div>
130
+ );
131
+ };
@@ -0,0 +1,85 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Header } from './Header';
3
+
4
+ const meta = {
5
+ title: 'Header/Header',
6
+ component: Header,
7
+ tags: ['autodocs'],
8
+ parameters: {},
9
+ args: {},
10
+ } satisfies Meta<typeof Header>;
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ export const Default: Story = {
16
+ args: {},
17
+ };
18
+
19
+ export const Person: Story = {
20
+ args: {
21
+ expanded: true,
22
+ globalMenu: {
23
+ accounts: [
24
+ {
25
+ type: 'person',
26
+ name: 'Aurora Mikalsen',
27
+ selected: true,
28
+ },
29
+ ],
30
+ menu: [
31
+ {
32
+ icon: 'airplane',
33
+ size: 'lg',
34
+ label: 'Section 1',
35
+ },
36
+ {
37
+ icon: 'briefcase',
38
+ size: 'lg',
39
+ label: 'Section 2',
40
+ },
41
+ {
42
+ size: 'lg',
43
+ label: 'Section 3',
44
+ icon: 'camera',
45
+ },
46
+ ],
47
+ },
48
+ },
49
+ };
50
+
51
+ export const Company: Story = {
52
+ args: {
53
+ globalMenu: {
54
+ accounts: [
55
+ {
56
+ type: 'company',
57
+ name: 'Bergen bar',
58
+ selected: true,
59
+ },
60
+ {
61
+ type: 'person',
62
+ name: 'Aurora Mikalsen',
63
+ },
64
+ ],
65
+ },
66
+ },
67
+ };
68
+
69
+ export const CompanyAndMenu: Story = {
70
+ args: {
71
+ globalMenu: {
72
+ accounts: [
73
+ {
74
+ type: 'company',
75
+ name: 'Bergen bar',
76
+ selected: true,
77
+ },
78
+ {
79
+ type: 'person',
80
+ name: 'Aurora Mikalsen',
81
+ },
82
+ ],
83
+ },
84
+ },
85
+ };
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+ import { useState } from 'react';
3
+ import { GlobalMenu, type GlobalMenuProps } from './GlobalMenu';
4
+ import { HeaderLogo } from './HeaderLogo';
5
+ import { HeaderSearch, type HeaderSearchProps } from './HeaderSearch';
6
+ import styles from './header.module.css';
7
+
8
+ export type HeaderColor = 'default' | 'dark' | 'light';
9
+
10
+ export interface HeaderAccountProps {
11
+ type?: string;
12
+ name?: string;
13
+ }
14
+
15
+ type ExpandedType = 'search' | 'account';
16
+
17
+ export interface HeaderProps {
18
+ color?: HeaderColor;
19
+ menu: GlobalMenuProps;
20
+ search?: HeaderSearchProps;
21
+ }
22
+
23
+ export const Header = ({ color, search, menu }: HeaderProps) => {
24
+ const [expandedType, setExpandedType] = useState<ExpandedType | null>(null);
25
+
26
+ const onToggle = (type: ExpandedType) => {
27
+ if (expandedType === type) {
28
+ setExpandedType(null);
29
+ } else {
30
+ setExpandedType(type);
31
+ }
32
+ };
33
+
34
+ const onSearchFocus = () => {
35
+ setExpandedType('search');
36
+ };
37
+
38
+ const onSearchBlur = () => {
39
+ setExpandedType(null);
40
+ };
41
+
42
+ return (
43
+ <header className={styles.header} data-color={color}>
44
+ <HeaderLogo className={styles?.logo} />
45
+ {menu && (
46
+ <GlobalMenu
47
+ {...menu}
48
+ expanded={expandedType === 'account'}
49
+ onToggle={() => onToggle('account')}
50
+ className={styles?.button}
51
+ />
52
+ )}
53
+ {search && (
54
+ <HeaderSearch
55
+ {...search}
56
+ className={styles?.search}
57
+ expanded={expandedType === 'search'}
58
+ onBlur={onSearchBlur}
59
+ onFocus={onSearchFocus}
60
+ />
61
+ )}
62
+ </header>
63
+ );
64
+ };
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ import styles from './header.module.css';
3
+
4
+ export interface HeaderBaseProps {
5
+ children?: ReactNode;
6
+ }
7
+
8
+ export const HeaderBase = ({ children }: HeaderBaseProps) => {
9
+ return <header className={styles.header}>{children}</header>;
10
+ };
@@ -0,0 +1,54 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { HeaderButton } from './HeaderButton';
4
+
5
+ const meta = {
6
+ title: 'Header/HeaderButton',
7
+ component: HeaderButton,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ args: {},
13
+ } satisfies Meta<typeof HeaderButton>;
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+
18
+ export const Default: Story = {
19
+ args: {},
20
+ };
21
+
22
+ export const Icon: Story = {
23
+ args: {
24
+ icon: 'inbox',
25
+ },
26
+ };
27
+
28
+ export const Person: Story = {
29
+ args: {
30
+ avatar: {
31
+ type: 'person',
32
+ name: 'Aurora Mikalsen',
33
+ },
34
+ },
35
+ };
36
+
37
+ export const Company: Story = {
38
+ args: {
39
+ avatar: {
40
+ type: 'company',
41
+ name: 'Bergen bar',
42
+ },
43
+ },
44
+ };
45
+
46
+ export const Expanded: Story = {
47
+ args: {
48
+ expanded: true,
49
+ avatar: {
50
+ type: 'company',
51
+ name: 'Bergen bar',
52
+ },
53
+ },
54
+ };
@@ -0,0 +1,55 @@
1
+ import cx from 'classnames';
2
+ import type { ElementType } from 'react';
3
+ import { Avatar, type AvatarProps } from '../Avatar';
4
+ import { ButtonBase, type ButtonProps } from '../Button';
5
+ import { Icon, type IconName } from '../Icon';
6
+
7
+ import styles from './headerButton.module.css';
8
+
9
+ export interface HeaderButtonProps extends ButtonProps {
10
+ label?: string;
11
+ avatar?: AvatarProps;
12
+ as?: ElementType;
13
+ className?: string;
14
+ expanded?: boolean;
15
+ icon?: IconName;
16
+ }
17
+
18
+ export const HeaderButton = ({
19
+ className,
20
+ as = 'button',
21
+ avatar,
22
+ icon = 'padlock-locked',
23
+ expanded,
24
+ label = 'Menu',
25
+ ...buttonProps
26
+ }: HeaderButtonProps) => {
27
+ if (expanded) {
28
+ return (
29
+ <ButtonBase {...buttonProps} as={as} className={cx(styles.button, className)}>
30
+ <span className={styles.label}>{label}</span>
31
+ <span className={cx(styles.icon, styles.closeIcon)}>
32
+ <Icon name={'x-mark'} />
33
+ </span>
34
+ </ButtonBase>
35
+ );
36
+ }
37
+
38
+ if (avatar) {
39
+ return (
40
+ <ButtonBase {...buttonProps} as={as} className={cx(styles.button, className)}>
41
+ <span className={styles.label}>{label}</span>
42
+ <Avatar type={avatar?.type} name={avatar?.name} size="lg" />
43
+ </ButtonBase>
44
+ );
45
+ }
46
+
47
+ return (
48
+ <ButtonBase {...buttonProps} as={as} className={cx(styles.button, className)}>
49
+ <span className={styles.label}>{label}</span>
50
+ <span className={cx(styles.icon, styles.loginIcon)}>
51
+ <Icon name={icon} />
52
+ </span>
53
+ </ButtonBase>
54
+ );
55
+ };
@@ -0,0 +1,17 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { HeaderLogo } from './HeaderLogo';
3
+
4
+ const meta = {
5
+ title: 'Header/HeaderLogo',
6
+ component: HeaderLogo,
7
+ tags: ['autodocs'],
8
+ parameters: {},
9
+ args: {},
10
+ } satisfies Meta<typeof HeaderLogo>;
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ export const Default: Story = {
16
+ args: {},
17
+ };
@@ -0,0 +1,22 @@
1
+ import cx from 'classnames';
2
+ import type { ElementType, MouseEventHandler } from 'react';
3
+ import { ButtonBase } from '../Button';
4
+ import { DigdirLogomark } from './DigdirLogomark.tsx';
5
+ import styles from './headerLogo.module.css';
6
+
7
+ export interface HeaderLogoProps {
8
+ className?: string;
9
+ as?: ElementType;
10
+ href?: string;
11
+ onClick?: MouseEventHandler<HTMLButtonElement>;
12
+ title?: string;
13
+ }
14
+
15
+ export const HeaderLogo = ({ className, as = 'a', title = 'Altinn', href = '/' }: HeaderLogoProps) => {
16
+ return (
17
+ <ButtonBase as={as} className={cx(styles.logo, className)} href={href}>
18
+ <DigdirLogomark className={styles.symbol} />
19
+ <span className={styles.text}>{title}</span>
20
+ </ButtonBase>
21
+ );
22
+ };
@@ -0,0 +1,20 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { HeaderSearch } from './HeaderSearch';
3
+
4
+ const meta = {
5
+ title: 'Header/HeaderSearch',
6
+ component: HeaderSearch,
7
+ tags: ['autodocs'],
8
+ parameters: {},
9
+ args: {},
10
+ } satisfies Meta<typeof HeaderSearch>;
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ export const Default: Story = {
16
+ args: {
17
+ placeholder: 'Search',
18
+ name: 'search',
19
+ },
20
+ };
@@ -0,0 +1,44 @@
1
+ import cx from 'classnames';
2
+ import type { ChangeEventHandler, FocusEventHandler } from 'react';
3
+ import { Icon } from '../Icon';
4
+ import styles from './headerSearch.module.css';
5
+
6
+ export interface HeaderSearchProps {
7
+ className?: string;
8
+ expanded?: boolean;
9
+ placeholder?: string;
10
+ name: string;
11
+ value?: string;
12
+ onFocus?: FocusEventHandler;
13
+ onBlur?: FocusEventHandler;
14
+ onChange?: ChangeEventHandler;
15
+ }
16
+
17
+ export const HeaderSearch = ({
18
+ className,
19
+ expanded = false,
20
+ name = 'q',
21
+ value,
22
+ placeholder = 'Søk',
23
+ onFocus,
24
+ onBlur,
25
+ onChange,
26
+ }: HeaderSearchProps) => {
27
+ return (
28
+ <div className={cx(styles.form, className)} aria-expanded={expanded}>
29
+ <div className={styles.field}>
30
+ <input
31
+ onFocus={onFocus}
32
+ onBlur={onBlur}
33
+ name={name}
34
+ value={value}
35
+ onChange={onChange}
36
+ placeholder={placeholder}
37
+ className={styles?.input}
38
+ type="search"
39
+ />
40
+ <Icon name="magnifying-glass" className={styles.icon} />
41
+ </div>
42
+ </div>
43
+ );
44
+ };