@altinn/altinn-components 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) 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/lib/components/Button/ButtonLabel.tsx +4 -2
  6. package/lib/components/Dialog/Dialog.stories.ts +12 -5
  7. package/lib/components/Dropdown/DropdownBase.tsx +14 -2
  8. package/lib/components/Dropdown/dropdownBase.module.css +5 -1
  9. package/lib/components/GlobalMenu/AccountButton.tsx +30 -0
  10. package/lib/components/GlobalMenu/AccountMenu.stories.tsx +65 -0
  11. package/lib/components/GlobalMenu/AccountMenu.tsx +82 -0
  12. package/lib/components/GlobalMenu/BackButton.tsx +10 -0
  13. package/lib/components/GlobalMenu/GlobalMenu.stories.tsx +110 -119
  14. package/lib/components/GlobalMenu/GlobalMenu.tsx +50 -91
  15. package/lib/components/GlobalMenu/GlobalMenuBase.tsx +22 -0
  16. package/lib/components/GlobalMenu/LogoutButton.tsx +19 -0
  17. package/lib/components/GlobalMenu/globalMenuBase.module.css +39 -0
  18. package/lib/components/GlobalMenu/index.tsx +1 -1
  19. package/lib/components/GlobalMenu/logoutButton.module.css +9 -0
  20. package/lib/components/Header/Header.stories.tsx +24 -8
  21. package/lib/components/Header/Header.tsx +12 -10
  22. package/lib/components/Layout/Layout.stories.tsx +7 -2
  23. package/lib/components/Menu/MenuItem.tsx +2 -0
  24. package/lib/components/Menu/MenuItemBase.tsx +8 -2
  25. package/lib/components/Menu/menuItemBase.module.css +6 -1
  26. package/lib/components/Menu/menuItemLabel.module.css +11 -1
  27. package/lib/components/Menu/menuSearch.module.css +1 -0
  28. package/lib/stories/Inbox/InboxLayout.tsx +1 -0
  29. package/lib/stories/Inbox/InboxProvider.tsx +0 -1
  30. package/package.json +16 -14
  31. package/lib/components/Menu/__MenuGroup.tsx +0 -18
  32. package/lib/components/Menu/__menuItem.module.css +0 -130
@@ -19,7 +19,7 @@ export const StoryDecorator = ({
19
19
  return (
20
20
  <RootProvider>
21
21
  <div className={styles.preview} data-theme={theme}>
22
- <div className={styles.component}>{children}</div>
22
+ <div id="story-in-story-decorator-root" className={styles.component}>{children}</div>
23
23
  <span className={styles.tag} data-tag={state}>
24
24
  {state}
25
25
  </span>
@@ -8,14 +8,13 @@ const config: StorybookConfig = {
8
8
  "@storybook/addon-essentials",
9
9
  "@storybook/addon-themes",
10
10
  "@chromatic-com/storybook",
11
- "@storybook/addon-interactions"
11
+ "@storybook/addon-interactions",
12
+ "@storybook/addon-a11y"
12
13
  ],
13
14
  framework: {
14
15
  name: "@storybook/react-vite",
15
16
  options: {},
16
17
  },
17
- docs: {
18
- autodocs: "tag",
19
- },
18
+ docs: {},
20
19
  };
21
20
  export default config;
@@ -2,9 +2,35 @@ import { withThemeByDataAttribute } from "@storybook/addon-themes";
2
2
  import { Preview, StoryFn } from "@storybook/react";
3
3
  import { StoryDecorator } from "./StoryDecorator";
4
4
  import "../lib/css/global.css";
5
+ import {A11yParameters} from "@storybook/addon-a11y";
6
+ import { Rule, getRules } from "axe-core";
7
+
5
8
  /** @type { import('@storybook/react').Preview } */
9
+
10
+ const enabledTags = [
11
+ "wcag2a",
12
+ "wcag2aa",
13
+ "wcag21a",
14
+ "wcag21aa",
15
+ "wcag22aa",
16
+ "best-practice",
17
+ ];
18
+
19
+ const enabledRules: Rule[] = getRules(enabledTags).map((ruleMetadata) => ({
20
+ id: ruleMetadata.ruleId,
21
+ enabled: true
22
+ }));
23
+
24
+ const a11y: A11yParameters = {
25
+ config: {
26
+ rules: enabledRules,
27
+ },
28
+ element: '#story-in-story-decorator-root',
29
+ };
30
+
6
31
  const preview: Preview = {
7
32
  parameters: {
33
+ a11y,
8
34
  controls: {
9
35
  matchers: {
10
36
  color: /(background|color)$/i,
@@ -32,4 +58,6 @@ const preview: Preview = {
32
58
  }),
33
59
  ],
34
60
  };
61
+
62
+
35
63
  export default preview;
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.2](https://github.com/Altinn/altinn-components/compare/v0.5.1...v0.5.2) (2024-11-18)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * fixing selecting current account and simplify API for Header ([#58](https://github.com/Altinn/altinn-components/issues/58)) ([b39a14a](https://github.com/Altinn/altinn-components/commit/b39a14a14014ff78209d636f3edc93507b443fd2))
9
+
10
+ ## [0.5.1](https://github.com/Altinn/altinn-components/compare/v0.5.0...v0.5.1) (2024-11-16)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * add log out button and refactoring of global menu ([#50](https://github.com/Altinn/altinn-components/issues/50)) ([0fc8bfa](https://github.com/Altinn/altinn-components/commit/0fc8bfaa0feb113624da9e740aed54d61de15d4c))
16
+
3
17
  ## [0.5.0](https://github.com/Altinn/altinn-components/compare/v0.4.1...v0.5.0) (2024-11-15)
4
18
 
5
19
 
@@ -1,15 +1,17 @@
1
+ import cx from 'classnames';
1
2
  import type { ReactNode } from 'react';
2
3
  import type { ButtonSize } from './ButtonBase';
3
4
  import styles from './buttonLabel.module.css';
4
5
 
5
6
  export interface ButtonLabelProps {
6
7
  size: ButtonSize;
8
+ className?: string;
7
9
  children: ReactNode;
8
10
  }
9
11
 
10
- export const ButtonLabel = ({ size, children }: ButtonLabelProps) => {
12
+ export const ButtonLabel = ({ className, size, children }: ButtonLabelProps) => {
11
13
  return (
12
- <span className={styles.label} data-size={size}>
14
+ <span className={cx(styles.label, className)} data-size={size}>
13
15
  {children}
14
16
  </span>
15
17
  );
@@ -8,11 +8,15 @@ const meta = {
8
8
  parameters: {},
9
9
  argTypes: { body: { control: 'text' } },
10
10
  args: {
11
- menu: [
12
- {
13
- items: [{ label: 'Menu 1' }],
14
- },
15
- ],
11
+ menu: {
12
+ id: 'context-menu',
13
+ items: [
14
+ {
15
+ label: 'Fjern',
16
+ id: 'remove',
17
+ },
18
+ ],
19
+ },
16
20
  updatedAt: '1999-05-26',
17
21
  updatedAtLabel: '26. mai 1999',
18
22
  title: 'Title',
@@ -42,9 +46,11 @@ export const Attachments: Story = {
42
46
  items: [
43
47
  {
44
48
  label: 'Dokument 1.pdf',
49
+ href: '',
45
50
  },
46
51
  {
47
52
  label: 'Dokument 2.pdf',
53
+ href: '',
48
54
  },
49
55
  ],
50
56
  },
@@ -106,6 +112,7 @@ export const Example: Story = {
106
112
  items: [
107
113
  {
108
114
  label: 'Vedtak om innlevering av bedriftsdata.pdf',
115
+ href: '',
109
116
  },
110
117
  ],
111
118
  },
@@ -6,14 +6,26 @@ export type DropdownPlacement = 'left' | 'right';
6
6
 
7
7
  export interface DropdownBaseProps {
8
8
  placement?: DropdownPlacement;
9
+ padding?: boolean;
9
10
  expanded?: boolean;
10
11
  className?: string;
11
12
  children?: ReactNode;
12
13
  }
13
14
 
14
- export const DropdownBase = ({ placement = 'left', expanded = false, className, children }: DropdownBaseProps) => {
15
+ export const DropdownBase = ({
16
+ placement = 'left',
17
+ padding = true,
18
+ expanded = false,
19
+ className,
20
+ children,
21
+ }: DropdownBaseProps) => {
15
22
  return (
16
- <div className={cx(styles.dropdown, className)} data-placement={placement} aria-expanded={expanded}>
23
+ <div
24
+ className={cx(styles.dropdown, className)}
25
+ data-placement={placement}
26
+ data-padding={padding}
27
+ aria-expanded={expanded}
28
+ >
17
29
  {children}
18
30
  </div>
19
31
  );
@@ -16,13 +16,17 @@
16
16
  right: 0;
17
17
  }
18
18
 
19
+ .dropdown[data-padding="trur"] {
20
+ padding: 0 0.5rem;
21
+ }
22
+
19
23
  .dropdown {
20
24
  min-width: 14rem;
21
25
  margin-top: 0.5rem;
22
- padding: 0 0.5rem;
23
26
  background-color: var(--neutral-background-default);
24
27
  border-radius: 0.375rem;
25
28
  box-shadow: var(--ds-shadow-md);
29
+ overflow: hidden;
26
30
  }
27
31
 
28
32
  .drawer .button {
@@ -0,0 +1,30 @@
1
+ import { MenuItemBase, MenuItemLabel, MenuItemMedia } from '../Menu';
2
+
3
+ export type Account = {
4
+ id: string;
5
+ type: 'person' | 'company';
6
+ name: string;
7
+ description?: string;
8
+ };
9
+
10
+ export type AccountButtonProps = {
11
+ account: Account;
12
+ description?: string;
13
+ linkText?: string;
14
+ onClick?: () => void;
15
+ };
16
+
17
+ export const AccountButton = ({ account, linkText, onClick }: AccountButtonProps) => {
18
+ return (
19
+ <MenuItemBase size="lg" onClick={onClick} linkText={linkText} linkIcon="arrow-right">
20
+ <MenuItemMedia
21
+ size="lg"
22
+ avatar={{
23
+ name: account.name,
24
+ type: account.type,
25
+ }}
26
+ />
27
+ <MenuItemLabel size="lg" title={account?.name} description={account?.description} />
28
+ </MenuItemBase>
29
+ );
30
+ };
@@ -0,0 +1,65 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState } from 'react';
3
+ import { AccountMenu } from './AccountMenu';
4
+
5
+ const meta = {
6
+ title: 'GlobalMenu/AccountMenu',
7
+ component: AccountMenu,
8
+ tags: ['autodocs'],
9
+ parameters: {},
10
+ args: {
11
+ accountGroups: {
12
+ primary: {
13
+ title: 'Deg selv og favoritter',
14
+ },
15
+ secondary: {
16
+ title: 'Andre kontoer',
17
+ },
18
+ },
19
+ accounts: [
20
+ {
21
+ groupId: 'primary',
22
+ type: 'person',
23
+ name: 'Aurora Mikalsen',
24
+ selected: true,
25
+ },
26
+ {
27
+ groupId: 'favourites',
28
+ type: 'company',
29
+ name: 'Bergen Bar',
30
+ selected: false,
31
+ },
32
+ {
33
+ groupId: 'secondary',
34
+ type: 'company',
35
+ name: 'Keeperhansker AS',
36
+ selected: false,
37
+ },
38
+ {
39
+ groupId: 'secondary',
40
+ type: 'company',
41
+ name: 'Stadion drift AS',
42
+ selected: false,
43
+ },
44
+ {
45
+ groupId: 'favourites',
46
+ type: 'company',
47
+ name: 'Sportsklubben Brann',
48
+ selected: false,
49
+ },
50
+ {
51
+ groupId: 'secondary',
52
+ type: 'company',
53
+ name: 'Landslaget',
54
+ selected: false,
55
+ },
56
+ ],
57
+ },
58
+ } satisfies Meta<typeof AccountMenu>;
59
+
60
+ export default meta;
61
+ type Story = StoryObj<typeof meta>;
62
+
63
+ export const Default: Story = {
64
+ args: {},
65
+ };
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+ import { useState } from 'react';
3
+ import { Menu, type MenuItemGroups, type MenuItemProps, type MenuSearchProps } from '../Menu';
4
+
5
+ export interface AccountSearch extends MenuSearchProps {
6
+ getResultsLabel?: (hits: number) => string;
7
+ hidden?: boolean;
8
+ }
9
+
10
+ export interface AccountMenuItem {
11
+ type: 'person' | 'company';
12
+ name: string;
13
+ id: string;
14
+ groupId?: string;
15
+ selected?: boolean;
16
+ }
17
+
18
+ export interface AccountMenuProps {
19
+ accounts?: AccountMenuItem[];
20
+ accountGroups?: MenuItemGroups;
21
+ accountSearch?: AccountSearch;
22
+ currentAccount?: AccountMenuItem;
23
+ onSelectAccount?: (id: string) => void;
24
+ }
25
+
26
+ const defaultResultLabel = (hits: number) => `${hits} hits`;
27
+
28
+ export const AccountMenu = ({
29
+ accounts = [],
30
+ accountGroups = {},
31
+ accountSearch,
32
+ onSelectAccount,
33
+ currentAccount,
34
+ }: AccountMenuProps) => {
35
+ const accountMenu: MenuItemProps[] = accounts.map((account) => ({
36
+ id: account.id || account.name,
37
+ groupId: account.groupId || 'search',
38
+ selected: account.selected ?? currentAccount?.id === account.id,
39
+ title: account.name,
40
+ avatar: {
41
+ type: account.type,
42
+ name: account.name,
43
+ },
44
+ onClick: () => onSelectAccount?.(account.id || account.name),
45
+ }));
46
+
47
+ const [filterString, setFilterString] = useState<string>('');
48
+
49
+ const filteredAccountMenu = filterString
50
+ ? accountMenu
51
+ .filter((item) => item?.title?.toLowerCase().includes(filterString.toLowerCase()))
52
+ .map((item) => {
53
+ return {
54
+ ...item,
55
+ groupId: 'search',
56
+ };
57
+ })
58
+ : accountMenu;
59
+
60
+ const filterAccountGroups = filterString
61
+ ? {
62
+ search: {
63
+ title:
64
+ accountSearch?.getResultsLabel?.(filteredAccountMenu.length) ??
65
+ defaultResultLabel(filteredAccountMenu.length),
66
+ },
67
+ }
68
+ : accountGroups;
69
+
70
+ const accountSearchItem: MenuSearchProps = {
71
+ name: 'account-search',
72
+ value: filterString,
73
+ placeholder: accountSearch?.placeholder ?? 'Find account',
74
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) => setFilterString(event.target.value),
75
+ };
76
+
77
+ const accountSwitcher: MenuItemProps[] = [
78
+ ...(filteredAccountMenu.length > 0 ? filteredAccountMenu : [{ id: 'search', groupId: 'search', hidden: true }]),
79
+ ];
80
+
81
+ return <Menu theme="global" search={accountSearchItem} groups={filterAccountGroups} items={accountSwitcher} />;
82
+ };
@@ -0,0 +1,10 @@
1
+ import { MenuItem } from '../Menu';
2
+
3
+ export interface BackButtonProps {
4
+ label: string;
5
+ onClick?: () => void;
6
+ }
7
+
8
+ export const BackButton = ({ label, onClick }: BackButtonProps) => {
9
+ return <MenuItem id="back" icon="arrow-left" title={label} onClick={onClick} />;
10
+ };
@@ -3,11 +3,62 @@ import { useState } from 'react';
3
3
  import { GlobalMenu } from './GlobalMenu';
4
4
 
5
5
  const meta = {
6
- title: 'Header/GlobalMenu',
6
+ title: 'GlobalMenu/GlobalMenu',
7
7
  component: GlobalMenu,
8
8
  tags: ['autodocs'],
9
9
  parameters: {},
10
10
  args: {
11
+ accountGroups: {
12
+ primary: {
13
+ title: 'Deg selv og favoritter',
14
+ },
15
+ secondary: {
16
+ title: 'Andre kontoer',
17
+ },
18
+ },
19
+ currentAccount: {
20
+ type: 'person',
21
+ name: 'Mathias Dyngeland',
22
+ description: 'Fødselsnr. 07101995 XXXXXX',
23
+ },
24
+ accounts: [
25
+ {
26
+ id: 'party:mathias',
27
+ groupId: 'primary',
28
+ type: 'person',
29
+ name: 'Mathias Dyngeland',
30
+ },
31
+ {
32
+ id: 'party:bergerbar',
33
+ groupId: 'favourites',
34
+ type: 'company',
35
+ name: 'Bergen bar',
36
+ },
37
+ {
38
+ id: 'party:keeperhansker',
39
+ groupId: 'secondary',
40
+ type: 'company',
41
+ name: 'Keeperhansker AS',
42
+ },
43
+ {
44
+ id: 'party:stadiondrift',
45
+ groupId: 'secondary',
46
+ type: 'company',
47
+ name: 'Stadion drift AS',
48
+ },
49
+ {
50
+ id: 'party:brann',
51
+ groupId: 'favourites',
52
+ type: 'company',
53
+ name: 'Sportsklubben Brann',
54
+ },
55
+ {
56
+ id: 'party:landslaget',
57
+ groupId: 'secondary',
58
+ type: 'company',
59
+ name: 'Landslaget',
60
+ },
61
+ ],
11
62
  groups: {
12
63
  apps: {
13
64
  divider: true,
@@ -19,14 +70,45 @@ const meta = {
19
70
  groupId: 'apps',
20
71
  size: 'lg',
21
72
  icon: 'inbox',
22
- label: 'Innboks',
73
+ title: 'Innboks',
74
+ badge: {
75
+ color: 'alert',
76
+ label: '4',
77
+ },
23
78
  },
24
79
  {
25
- id: 'settings',
80
+ id: 'access',
26
81
  groupId: 'apps',
27
82
  size: 'lg',
28
- icon: 'cog',
29
- label: 'Settings',
83
+ icon: 'bookmark',
84
+ title: 'Tilganger',
85
+ badge: {
86
+ color: 'alert',
87
+ label: '2',
88
+ },
89
+ },
90
+ {
91
+ id: 'access',
92
+ groupId: 'apps',
93
+ size: 'lg',
94
+ icon: 'menu-grid',
95
+ title: 'Alle skjema',
96
+ },
97
+ {
98
+ id: 'startup',
99
+ groupId: 'help',
100
+ color: 'neutral',
101
+ size: 'sm',
102
+ icon: 'buildings2',
103
+ title: 'Starte og drive bedrift',
104
+ },
105
+ {
106
+ id: 'help',
107
+ groupId: 'help',
108
+ size: 'sm',
109
+ color: 'neutral',
110
+ icon: 'chat-exclamationmark',
111
+ title: 'Trenger du hjelp?',
30
112
  },
31
113
  ],
32
114
  },
@@ -39,43 +121,31 @@ export const Default: Story = {};
39
121
 
40
122
  export const Login: Story = {
41
123
  args: {
42
- expanded: true,
124
+ currentAccount: undefined,
43
125
  items: [
44
126
  {
45
127
  id: 'login',
128
+ groupId: 'login',
46
129
  size: 'lg',
130
+ color: 'strong',
47
131
  icon: 'padlock-locked',
48
132
  title: 'Logg inn',
49
133
  },
50
134
  {
51
- id: 'help',
52
- size: 'lg',
53
- icon: 'chat-exclamationmark',
54
- title: 'Hjelp',
135
+ id: 'startup',
136
+ groupId: 'help',
137
+ color: 'neutral',
138
+ size: 'sm',
139
+ icon: 'buildings2',
140
+ title: 'Starte og drive bedrift',
55
141
  },
56
- ],
57
- },
58
- };
59
-
60
- export const ControlledStateLogin = () => {
61
- const [expanded, setExpanded] = useState<boolean>(false);
62
- return (
63
- <GlobalMenu
64
- {...Login.args}
65
- expanded={expanded}
66
- onToggle={() => setExpanded((prevState) => !prevState)}
67
- items={Login.args.items ?? []}
68
- />
69
- );
70
- };
71
-
72
- export const Person: Story = {
73
- args: {
74
- accounts: [
75
142
  {
76
- type: 'person',
77
- name: 'Aurora Mikalsen',
78
- selected: true,
143
+ id: 'help',
144
+ groupId: 'help',
145
+ color: 'neutral',
146
+ size: 'sm',
147
+ icon: 'chat-exclamationmark',
148
+ title: 'Trenger du hjelp?',
79
149
  },
80
150
  ],
81
151
  },
@@ -83,46 +153,20 @@ export const Person: Story = {
83
153
 
84
154
  export const Company: Story = {
85
155
  args: {
86
- accounts: [
87
- {
88
- type: 'company',
89
- name: 'Bergen bar',
90
- selected: true,
91
- },
92
- ],
93
- },
94
- };
95
-
96
- export const Expanded: Story = {
97
- args: {
98
- expanded: true,
99
- accounts: [
100
- {
101
- type: 'company',
102
- name: 'Bergen bar',
103
- selected: true,
104
- },
105
- ],
106
- },
107
- };
108
-
109
- export const CustomLabel: Story = {
110
- args: {
111
- menuLabel: 'Meny',
112
- accounts: [
113
- {
114
- type: 'person',
115
- name: 'Aurora Mikalsen',
116
- selected: true,
117
- },
118
- ],
156
+ currentAccount: {
157
+ type: 'company',
158
+ name: 'Sportsklubben Brann',
159
+ description: 'Org. nr. 934908988',
160
+ },
119
161
  },
120
162
  };
121
163
 
122
- export const Accounts: Story = {
164
+ export const CustomLabels: Story = {
123
165
  args: {
166
+ logoutLabel: 'Logg ut',
124
167
  menuLabel: 'Meny',
125
168
  backLabel: 'Tilbake',
169
+ changeLabel: 'Endre konto',
126
170
  accountSearch: {
127
171
  placeholder: 'Søk etter konto',
128
172
  getResultsLabel: (hits = 0) => {
@@ -133,58 +177,5 @@ export const Accounts: Story = {
133
177
  },
134
178
  hidden: false,
135
179
  },
136
- expanded: true,
137
- accountGroups: {
138
- primary: {
139
- title: 'Deg selv og favoritter',
140
- },
141
- secondary: {
142
- title: 'Andre kontoer',
143
- },
144
- },
145
- accounts: [
146
- {
147
- groupId: 'primary',
148
- type: 'person',
149
- name: 'Aurora Mikalsen',
150
- selected: true,
151
- },
152
- {
153
- groupId: 'favourites',
154
- type: 'person',
155
- name: 'Rakel Engelsvik',
156
- selected: false,
157
- },
158
- {
159
- groupId: 'favourites',
160
- type: 'company',
161
- name: 'Auroras keeperskole',
162
- selected: false,
163
- },
164
- {
165
- groupId: 'secondary',
166
- type: 'company',
167
- name: 'Keeperhansker AS',
168
- selected: false,
169
- },
170
- {
171
- groupId: 'secondary',
172
- type: 'company',
173
- name: 'Stadion drift AS',
174
- selected: false,
175
- },
176
- {
177
- groupId: 'secondary',
178
- type: 'company',
179
- name: 'Sportsklubben Brann',
180
- selected: false,
181
- },
182
- {
183
- groupId: 'secondary',
184
- type: 'company',
185
- name: 'Landslaget',
186
- selected: false,
187
- },
188
- ],
189
180
  },
190
181
  };