@altinn/altinn-components 0.5.0 → 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 +7 -0
- package/lib/components/Button/ButtonLabel.tsx +4 -2
- package/lib/components/Dialog/Dialog.stories.ts +12 -5
- package/lib/components/Dropdown/DropdownBase.tsx +14 -2
- package/lib/components/Dropdown/dropdownBase.module.css +5 -1
- 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 +110 -119
- package/lib/components/GlobalMenu/GlobalMenu.tsx +40 -88
- 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.tsx +3 -3
- package/lib/components/Menu/MenuItem.tsx +2 -0
- package/lib/components/Menu/MenuItemBase.tsx +8 -2
- package/lib/components/Menu/menuItemBase.module.css +6 -1
- package/lib/components/Menu/menuItemLabel.module.css +11 -1
- package/lib/components/Menu/menuSearch.module.css +1 -0
- package/package.json +15 -13
- package/lib/components/Menu/__MenuGroup.tsx +0 -18
- 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>
|
package/.storybook/main.ts
CHANGED
|
@@ -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;
|
package/.storybook/preview.tsx
CHANGED
|
@@ -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,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.1](https://github.com/Altinn/altinn-components/compare/v0.5.0...v0.5.1) (2024-11-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* 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))
|
|
9
|
+
|
|
3
10
|
## [0.5.0](https://github.com/Altinn/altinn-components/compare/v0.4.1...v0.5.0) (2024-11-15)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -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
|
-
|
|
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 = ({
|
|
15
|
+
export const DropdownBase = ({
|
|
16
|
+
placement = 'left',
|
|
17
|
+
padding = true,
|
|
18
|
+
expanded = false,
|
|
19
|
+
className,
|
|
20
|
+
children,
|
|
21
|
+
}: DropdownBaseProps) => {
|
|
15
22
|
return (
|
|
16
|
-
<div
|
|
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,29 @@
|
|
|
1
|
+
import { MenuItemBase, MenuItemLabel, MenuItemMedia } from '../Menu';
|
|
2
|
+
|
|
3
|
+
type Account = {
|
|
4
|
+
type: 'person' | 'company';
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type AccountButtonProps = {
|
|
10
|
+
account: Account;
|
|
11
|
+
description?: string;
|
|
12
|
+
linkText?: string;
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const AccountButton = ({ account, linkText, onClick }: AccountButtonProps) => {
|
|
17
|
+
return (
|
|
18
|
+
<MenuItemBase size="lg" onClick={onClick} linkText={linkText} linkIcon="arrow-right">
|
|
19
|
+
<MenuItemMedia
|
|
20
|
+
size="lg"
|
|
21
|
+
avatar={{
|
|
22
|
+
name: account.name,
|
|
23
|
+
type: account.type,
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
26
|
+
<MenuItemLabel size="lg" title={account?.name} description={account?.description} />
|
|
27
|
+
</MenuItemBase>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -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,73 @@
|
|
|
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
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaultResultLabel = (hits: number) => `${hits} hits`;
|
|
25
|
+
|
|
26
|
+
export const AccountMenu = ({ accounts = [], accountGroups = {}, accountSearch }: AccountMenuProps) => {
|
|
27
|
+
const accountMenu: MenuItemProps[] = accounts.map((account) => ({
|
|
28
|
+
id: account.name,
|
|
29
|
+
groupId: account.groupId || 'search',
|
|
30
|
+
selected: account.selected,
|
|
31
|
+
title: account.name,
|
|
32
|
+
avatar: {
|
|
33
|
+
type: account.type,
|
|
34
|
+
name: account.name,
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const [filterString, setFilterString] = useState<string>('');
|
|
39
|
+
|
|
40
|
+
const filteredAccountMenu = filterString
|
|
41
|
+
? accountMenu
|
|
42
|
+
.filter((item) => item?.title?.toLowerCase().includes(filterString.toLowerCase()))
|
|
43
|
+
.map((item) => {
|
|
44
|
+
return {
|
|
45
|
+
...item,
|
|
46
|
+
groupId: 'search',
|
|
47
|
+
};
|
|
48
|
+
})
|
|
49
|
+
: accountMenu;
|
|
50
|
+
|
|
51
|
+
const filterAccountGroups = filterString
|
|
52
|
+
? {
|
|
53
|
+
search: {
|
|
54
|
+
title:
|
|
55
|
+
accountSearch?.getResultsLabel?.(filteredAccountMenu.length) ??
|
|
56
|
+
defaultResultLabel(filteredAccountMenu.length),
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
: accountGroups;
|
|
60
|
+
|
|
61
|
+
const accountSearchItem: MenuSearchProps = {
|
|
62
|
+
name: 'account-search',
|
|
63
|
+
value: filterString,
|
|
64
|
+
placeholder: accountSearch?.placeholder ?? 'Find account',
|
|
65
|
+
onChange: (event: React.ChangeEvent<HTMLInputElement>) => setFilterString(event.target.value),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const accountSwitcher: MenuItemProps[] = [
|
|
69
|
+
...(filteredAccountMenu.length > 0 ? filteredAccountMenu : [{ id: 'search', groupId: 'search', hidden: true }]),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
return <Menu theme="global" search={accountSearchItem} groups={filterAccountGroups} items={accountSwitcher} />;
|
|
73
|
+
};
|
|
@@ -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: '
|
|
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
|
+
groupId: 'primary',
|
|
27
|
+
type: 'person',
|
|
28
|
+
name: 'Mathias Dyngeland',
|
|
29
|
+
selected: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
groupId: 'favourites',
|
|
33
|
+
type: 'company',
|
|
34
|
+
name: 'Bergen bar',
|
|
35
|
+
selected: false,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
groupId: 'secondary',
|
|
39
|
+
type: 'company',
|
|
40
|
+
name: 'Keeperhansker AS',
|
|
41
|
+
selected: false,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
groupId: 'secondary',
|
|
45
|
+
type: 'company',
|
|
46
|
+
name: 'Stadion drift AS',
|
|
47
|
+
selected: false,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
groupId: 'favourites',
|
|
51
|
+
type: 'company',
|
|
52
|
+
name: 'Sportsklubben Brann',
|
|
53
|
+
selected: false,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
groupId: 'secondary',
|
|
57
|
+
type: 'company',
|
|
58
|
+
name: 'Landslaget',
|
|
59
|
+
selected: false,
|
|
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
|
-
|
|
73
|
+
title: 'Innboks',
|
|
74
|
+
badge: {
|
|
75
|
+
color: 'alert',
|
|
76
|
+
label: '4',
|
|
77
|
+
},
|
|
23
78
|
},
|
|
24
79
|
{
|
|
25
|
-
id: '
|
|
80
|
+
id: 'access',
|
|
26
81
|
groupId: 'apps',
|
|
27
82
|
size: 'lg',
|
|
28
|
-
icon: '
|
|
29
|
-
|
|
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
|
-
|
|
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: '
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
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
|
};
|
|
@@ -1,122 +1,74 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { type MouseEventHandler, useState } from 'react';
|
|
3
|
-
import type
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import { Menu, type MenuItemGroups, type MenuItemProps } from '../Menu';
|
|
4
|
+
import { AccountButton } from './AccountButton';
|
|
5
|
+
import { AccountMenu, type AccountMenuProps } from './AccountMenu';
|
|
6
|
+
import { BackButton } from './BackButton';
|
|
7
|
+
import { GlobalMenuBase, GlobalMenuFooter, GlobalMenuHeader } from './GlobalMenuBase';
|
|
8
|
+
import { LogoutButton } from './LogoutButton';
|
|
9
|
+
|
|
10
|
+
export interface CurrentAccount {
|
|
11
|
+
type: 'person' | 'company';
|
|
8
12
|
name: string;
|
|
9
|
-
|
|
10
|
-
group?: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export interface AccountSearch extends MenuSearchProps {
|
|
14
|
-
getResultsLabel?: (hits: number) => string;
|
|
15
|
-
hidden?: boolean;
|
|
13
|
+
description?: string;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
export interface GlobalMenuProps {
|
|
21
|
-
variant: MobileMenuType;
|
|
16
|
+
export interface GlobalMenuProps extends AccountMenuProps {
|
|
17
|
+
currentEndUser?: CurrentAccount;
|
|
22
18
|
expanded: boolean;
|
|
23
19
|
onToggle: MouseEventHandler;
|
|
24
20
|
items: MenuItemProps[];
|
|
25
21
|
groups?: MenuItemGroups;
|
|
26
|
-
accounts?: Account[];
|
|
27
|
-
accountGroups?: MenuItemGroups;
|
|
28
|
-
accountSearch?: AccountSearch;
|
|
29
22
|
menuLabel?: string;
|
|
30
23
|
backLabel?: string;
|
|
24
|
+
changeLabel?: string;
|
|
25
|
+
logoutLabel?: string;
|
|
31
26
|
className?: string;
|
|
32
27
|
}
|
|
33
28
|
|
|
34
|
-
const defaultResultLabel = (hits: number) => `${hits} hits`;
|
|
35
|
-
|
|
36
29
|
export const GlobalMenu = ({
|
|
30
|
+
currentEndUser,
|
|
37
31
|
accounts = [],
|
|
38
32
|
accountGroups = {},
|
|
39
33
|
accountSearch,
|
|
40
34
|
items = [],
|
|
41
35
|
groups,
|
|
36
|
+
changeLabel = 'Change',
|
|
37
|
+
logoutLabel = 'Logout',
|
|
42
38
|
backLabel = 'Back',
|
|
43
39
|
}: GlobalMenuProps) => {
|
|
44
|
-
const accountMenu: MenuItemProps[] = accounts.map((account) => ({
|
|
45
|
-
id: account.name,
|
|
46
|
-
group: account.group || 'search',
|
|
47
|
-
selected: account.selected,
|
|
48
|
-
title: account.name,
|
|
49
|
-
avatar: {
|
|
50
|
-
type: account.type,
|
|
51
|
-
name: account.name,
|
|
52
|
-
},
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
|
-
const selectedAccount = accountMenu.find((account) => account.selected);
|
|
56
40
|
const [selectAccount, setSelectAccount] = useState<boolean>(false);
|
|
57
|
-
const [filterString, setFilterString] = useState<string>('');
|
|
58
41
|
|
|
59
42
|
const onToggleAccounts = () => {
|
|
60
43
|
setSelectAccount((prevState) => !prevState);
|
|
61
44
|
};
|
|
62
45
|
|
|
63
|
-
const accountMenuItem: MenuItemProps = {
|
|
64
|
-
...selectedAccount,
|
|
65
|
-
id: 'account',
|
|
66
|
-
selected: false,
|
|
67
|
-
size: 'lg',
|
|
68
|
-
onClick: onToggleAccounts,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const MobileMenu = selectedAccount ? [accountMenuItem, ...items] : items;
|
|
72
|
-
|
|
73
|
-
const filteredAccountMenu = filterString
|
|
74
|
-
? accountMenu
|
|
75
|
-
.filter((item) => item?.title?.toLowerCase().includes(filterString.toLowerCase()))
|
|
76
|
-
.map((item) => {
|
|
77
|
-
return {
|
|
78
|
-
...item,
|
|
79
|
-
groupId: 'search',
|
|
80
|
-
};
|
|
81
|
-
})
|
|
82
|
-
: accountMenu;
|
|
83
|
-
|
|
84
|
-
const filterAccountGroups = filterString
|
|
85
|
-
? {
|
|
86
|
-
search: {
|
|
87
|
-
title:
|
|
88
|
-
accountSearch?.getResultsLabel?.(filteredAccountMenu.length) ??
|
|
89
|
-
defaultResultLabel(filteredAccountMenu.length),
|
|
90
|
-
},
|
|
91
|
-
}
|
|
92
|
-
: accountGroups;
|
|
93
|
-
|
|
94
|
-
const accountSearchItem: MenuSearchProps = {
|
|
95
|
-
name: 'account-search',
|
|
96
|
-
value: filterString,
|
|
97
|
-
placeholder: accountSearch?.placeholder ?? 'Find account',
|
|
98
|
-
onChange: (event: React.ChangeEvent<HTMLInputElement>) => setFilterString(event.target.value),
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const backItem: MenuItemProps = {
|
|
102
|
-
id: 'back',
|
|
103
|
-
title: backLabel ?? 'Back',
|
|
104
|
-
icon: 'arrow-left',
|
|
105
|
-
onClick: onToggleAccounts,
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const accountSwitcher: MenuItemProps[] = [
|
|
109
|
-
...(filteredAccountMenu.length > 0 ? filteredAccountMenu : [{ id: 'search', groupId: 'search', hidden: true }]),
|
|
110
|
-
];
|
|
111
|
-
|
|
112
46
|
if (selectAccount) {
|
|
113
47
|
return (
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
|
|
48
|
+
<GlobalMenuBase>
|
|
49
|
+
<BackButton onClick={onToggleAccounts} label={backLabel} />
|
|
50
|
+
<AccountMenu accounts={accounts} accountGroups={accountGroups} accountSearch={accountSearch} />
|
|
51
|
+
</GlobalMenuBase>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (currentEndUser) {
|
|
56
|
+
return (
|
|
57
|
+
<GlobalMenuBase>
|
|
58
|
+
<GlobalMenuHeader>
|
|
59
|
+
<AccountButton account={currentEndUser} linkText={changeLabel} onClick={onToggleAccounts} />
|
|
60
|
+
</GlobalMenuHeader>
|
|
61
|
+
<Menu groups={groups} items={items} />
|
|
62
|
+
<GlobalMenuFooter>
|
|
63
|
+
<LogoutButton label={logoutLabel} />
|
|
64
|
+
</GlobalMenuFooter>
|
|
65
|
+
</GlobalMenuBase>
|
|
118
66
|
);
|
|
119
67
|
}
|
|
120
68
|
|
|
121
|
-
return
|
|
69
|
+
return (
|
|
70
|
+
<GlobalMenuBase>
|
|
71
|
+
<Menu groups={groups} items={items} />
|
|
72
|
+
</GlobalMenuBase>
|
|
73
|
+
);
|
|
122
74
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import styles from './globalMenuBase.module.css';
|
|
3
|
+
|
|
4
|
+
export interface GlobalMenuBaseProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const GlobalMenuBase = ({ children }: GlobalMenuBaseProps) => {
|
|
9
|
+
return (
|
|
10
|
+
<nav className={styles.nav} data-theme="global">
|
|
11
|
+
{children}
|
|
12
|
+
</nav>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const GlobalMenuHeader = ({ children }: GlobalMenuBaseProps) => {
|
|
17
|
+
return <header className={styles.header}>{children}</header>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const GlobalMenuFooter = ({ children }: GlobalMenuBaseProps) => {
|
|
21
|
+
return <footer className={styles.footer}>{children}</footer>;
|
|
22
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MouseEventHandler, ReactNode } from 'react';
|
|
2
|
+
import { ButtonBase, ButtonLabel } from '../Button';
|
|
3
|
+
import styles from './logoutButton.module.css';
|
|
4
|
+
|
|
5
|
+
export interface LogoutButtonProps {
|
|
6
|
+
label?: string;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
onClick?: MouseEventHandler;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const LogoutButton = ({ label = 'Logout', children, onClick }: LogoutButtonProps) => {
|
|
12
|
+
return (
|
|
13
|
+
<ButtonBase className={styles.button} variant="outline" size="lg" onClick={onClick}>
|
|
14
|
+
<ButtonLabel className={styles.label} size="lg">
|
|
15
|
+
{children || label}
|
|
16
|
+
</ButtonLabel>
|
|
17
|
+
</ButtonBase>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.nav {
|
|
2
|
+
background-color: #fff;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
padding: 0 0.5rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.header {
|
|
9
|
+
border-bottom: 1px solid var(--theme-border-subtle);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.footer {
|
|
13
|
+
border-top: 1px solid var(--theme-border-subtle);
|
|
14
|
+
padding: 1rem 0.5rem;
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
align-items: stretch;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.drawer[aria-expanded="true"] {
|
|
21
|
+
display: block;
|
|
22
|
+
padding: 0.5rem;
|
|
23
|
+
z-index: 2;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.dropdown[aria-expanded="true"] {
|
|
27
|
+
display: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (min-width: 1024px) {
|
|
31
|
+
.drawer[aria-expanded="true"] {
|
|
32
|
+
display: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.dropdown[aria-expanded="true"] {
|
|
36
|
+
display: block;
|
|
37
|
+
z-index: 2;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './GlobalMenu
|
|
1
|
+
export * from './GlobalMenu';
|
|
@@ -48,8 +48,8 @@ export const Header = ({ search, menu }: HeaderProps) => {
|
|
|
48
48
|
label={menu?.menuLabel}
|
|
49
49
|
/>
|
|
50
50
|
{menu && (
|
|
51
|
-
<DropdownBase placement="right" expanded={currentId === 'menu'} className={styles.dropdown}>
|
|
52
|
-
<GlobalMenu {...menu}
|
|
51
|
+
<DropdownBase padding={false} placement="right" expanded={currentId === 'menu'} className={styles.dropdown}>
|
|
52
|
+
<GlobalMenu {...menu} currentEndUser={selectedAccount} />
|
|
53
53
|
</DropdownBase>
|
|
54
54
|
)}
|
|
55
55
|
</HeaderMenu>
|
|
@@ -64,7 +64,7 @@ export const Header = ({ search, menu }: HeaderProps) => {
|
|
|
64
64
|
)}
|
|
65
65
|
{menu && (
|
|
66
66
|
<DrawerBase expanded={currentId === 'menu'} className={styles.drawer}>
|
|
67
|
-
<GlobalMenu {...menu}
|
|
67
|
+
<GlobalMenu {...menu} currentEndUser={selectedAccount} expanded={currentId === 'menu'} />
|
|
68
68
|
</DrawerBase>
|
|
69
69
|
)}
|
|
70
70
|
</HeaderBase>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import cx from 'classnames';
|
|
2
|
-
import type { ElementType, ReactNode } from 'react';
|
|
2
|
+
import type { ElementType, MouseEventHandler, ReactNode } from 'react';
|
|
3
3
|
import { Badge, type BadgeProps } from '../Badge';
|
|
4
4
|
import { Icon, type IconName } from '../Icon';
|
|
5
5
|
import styles from './menuItemBase.module.css';
|
|
@@ -9,11 +9,13 @@ export type MenuItemSize = 'sm' | 'md' | 'lg';
|
|
|
9
9
|
|
|
10
10
|
export interface MenuItemBaseProps {
|
|
11
11
|
as?: ElementType;
|
|
12
|
+
onClick?: MouseEventHandler;
|
|
12
13
|
color?: MenuItemColor;
|
|
13
14
|
children?: ReactNode;
|
|
14
15
|
tabIndex?: number;
|
|
15
16
|
size?: MenuItemSize;
|
|
16
17
|
linkIcon?: IconName;
|
|
18
|
+
linkText?: string;
|
|
17
19
|
badge?: BadgeProps;
|
|
18
20
|
collapsible?: boolean;
|
|
19
21
|
active?: boolean;
|
|
@@ -25,9 +27,11 @@ export interface MenuItemBaseProps {
|
|
|
25
27
|
|
|
26
28
|
export const MenuItemBase = ({
|
|
27
29
|
as,
|
|
30
|
+
onClick,
|
|
28
31
|
size,
|
|
29
32
|
color,
|
|
30
33
|
linkIcon,
|
|
34
|
+
linkText,
|
|
31
35
|
badge,
|
|
32
36
|
tabIndex = 0,
|
|
33
37
|
active = false,
|
|
@@ -54,6 +58,7 @@ export const MenuItemBase = ({
|
|
|
54
58
|
aria-expanded={expanded}
|
|
55
59
|
aria-disabled={disabled}
|
|
56
60
|
aria-selected={selected}
|
|
61
|
+
onClick={onClick}
|
|
57
62
|
className={cx(styles.item, className)}
|
|
58
63
|
{...rest}
|
|
59
64
|
>
|
|
@@ -63,7 +68,8 @@ export const MenuItemBase = ({
|
|
|
63
68
|
</div>
|
|
64
69
|
{applicableIcon && (
|
|
65
70
|
<div className={styles.action}>
|
|
66
|
-
|
|
71
|
+
<span className={styles.linkText}>{linkText}</span>
|
|
72
|
+
{applicableIcon && <Icon name={applicableIcon} className={styles.linkIcon} />}
|
|
67
73
|
</div>
|
|
68
74
|
)}
|
|
69
75
|
</Component>
|
|
@@ -4,12 +4,22 @@
|
|
|
4
4
|
padding: 0 0.25rem;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
.title {
|
|
8
|
+
color: var(--theme-text-default);
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
.title[data-size="lg"] {
|
|
8
12
|
font-size: 1.125rem;
|
|
9
13
|
line-height: 1.25;
|
|
10
14
|
font-weight: 500;
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
.title[data-size="md"] {
|
|
18
|
+
font-size: 1rem;
|
|
19
|
+
line-height: 1.25;
|
|
20
|
+
font-weight: 500;
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
.title[data-size="sm"] {
|
|
14
24
|
font-size: 1rem;
|
|
15
25
|
line-height: 1.25;
|
|
@@ -17,6 +27,6 @@
|
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
.description {
|
|
20
|
-
font-size:
|
|
30
|
+
font-size: 0.875rem;
|
|
21
31
|
color: var(--theme-text-subtle);
|
|
22
32
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@altinn/altinn-components",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"main": "lib/index.ts",
|
|
5
5
|
"description": "Reusable react components",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,23 +16,25 @@
|
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@biomejs/biome": "1.9.3",
|
|
19
|
-
"@chromatic-com/storybook": "^
|
|
20
|
-
"@storybook/addon-
|
|
21
|
-
"@storybook/addon-
|
|
22
|
-
"@storybook/addon-
|
|
23
|
-
"@storybook/addon-
|
|
24
|
-
"@storybook/addon-
|
|
25
|
-
"@storybook/
|
|
26
|
-
"@storybook/
|
|
27
|
-
"@storybook/react
|
|
28
|
-
"@storybook/
|
|
19
|
+
"@chromatic-com/storybook": "^3.2.2",
|
|
20
|
+
"@storybook/addon-a11y": "^8.4.4",
|
|
21
|
+
"@storybook/addon-essentials": "^8.4.4",
|
|
22
|
+
"@storybook/addon-interactions": "^8.4.4",
|
|
23
|
+
"@storybook/addon-links": "^8.4.4",
|
|
24
|
+
"@storybook/addon-onboarding": "^8.4.4",
|
|
25
|
+
"@storybook/addon-themes": "^8.4.4",
|
|
26
|
+
"@storybook/blocks": "^8.4.4",
|
|
27
|
+
"@storybook/react": "^8.4.4",
|
|
28
|
+
"@storybook/react-vite": "^8.4.4",
|
|
29
|
+
"@storybook/test": "^8.4.4",
|
|
29
30
|
"@types/react": "^18.3.11",
|
|
30
31
|
"@types/react-dom": "^18.3.1",
|
|
31
32
|
"@vitejs/plugin-react-swc": "^3.7.1",
|
|
33
|
+
"axe-core": "^4.10.2",
|
|
32
34
|
"lint-staged": "^15.2.10",
|
|
33
35
|
"prop-types": "^15.8.1",
|
|
34
|
-
"storybook": "^8.
|
|
35
|
-
"storybook-addon-theme-provider": "^0.2.
|
|
36
|
+
"storybook": "^8.4.4",
|
|
37
|
+
"storybook-addon-theme-provider": "^0.2.8",
|
|
36
38
|
"typescript": "^5.6.3",
|
|
37
39
|
"vite": "^5.4.9"
|
|
38
40
|
},
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ElementType, ReactNode } from 'react';
|
|
2
|
-
import styles from './menu.module.css';
|
|
3
|
-
|
|
4
|
-
export interface MenuGroupProps {
|
|
5
|
-
as?: ElementType;
|
|
6
|
-
expanded?: boolean;
|
|
7
|
-
divider?: boolean;
|
|
8
|
-
children?: ReactNode;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const MenuGroup = ({ as = 'div', expanded, divider = true, children }: MenuGroupProps) => {
|
|
12
|
-
const Component = as;
|
|
13
|
-
return (
|
|
14
|
-
<Component aria-expanded={expanded} data-divider={divider} className={styles.group}>
|
|
15
|
-
{children}
|
|
16
|
-
</Component>
|
|
17
|
-
);
|
|
18
|
-
};
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
.item {
|
|
2
|
-
background-color: transparent;
|
|
3
|
-
display: flex;
|
|
4
|
-
align-items: center;
|
|
5
|
-
column-gap: 4px;
|
|
6
|
-
border: 0;
|
|
7
|
-
user-select: none;
|
|
8
|
-
cursor: pointer;
|
|
9
|
-
margin: 0.5rem 0;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.item[aria-disabled="true"] {
|
|
13
|
-
opacity: 0.5;
|
|
14
|
-
pointer-events: none;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/* size */
|
|
18
|
-
|
|
19
|
-
.item[data-size="sm"] {
|
|
20
|
-
min-height: 44px;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/* content */
|
|
24
|
-
|
|
25
|
-
.content {
|
|
26
|
-
display: flex;
|
|
27
|
-
width: 100%;
|
|
28
|
-
align-items: center;
|
|
29
|
-
column-gap: 6px;
|
|
30
|
-
padding: 6px;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.action {
|
|
34
|
-
display: flex;
|
|
35
|
-
justify-content: center;
|
|
36
|
-
align-items: center;
|
|
37
|
-
padding: 10px;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.actionIcon {
|
|
41
|
-
font-size: 1.5rem;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.media {
|
|
45
|
-
flex-shrink: 0;
|
|
46
|
-
display: flex;
|
|
47
|
-
align-items: center;
|
|
48
|
-
justify-content: center;
|
|
49
|
-
border-radius: 5%;
|
|
50
|
-
width: 2rem;
|
|
51
|
-
height: 2rem;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.media[data-size="lg"] {
|
|
55
|
-
width: 44px;
|
|
56
|
-
height: 44px;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.icon {
|
|
60
|
-
font-size: 1.5rem;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/* label */
|
|
64
|
-
|
|
65
|
-
.label {
|
|
66
|
-
display: flex;
|
|
67
|
-
flex-direction: column;
|
|
68
|
-
padding: 0 0.25rem;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/* title */
|
|
72
|
-
|
|
73
|
-
.title[data-size="lg"] {
|
|
74
|
-
font-size: 1.125rem;
|
|
75
|
-
line-height: 1.25;
|
|
76
|
-
font-weight: 500;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.title[data-size="sm"] {
|
|
80
|
-
font-size: 1rem;
|
|
81
|
-
line-height: 1.25;
|
|
82
|
-
font-weight: 400;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.description {
|
|
86
|
-
font-size: 14px;
|
|
87
|
-
color: var(--theme-text-subtle);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.item:hover {
|
|
91
|
-
background-color: var(--theme-surface-default);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.item[aria-selected="true"] {
|
|
95
|
-
background-color: var(--theme-background-default);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.media[data-color="subtle"] {
|
|
99
|
-
background-color: var(--theme-background-default);
|
|
100
|
-
color: var(--theme-text-default);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.media[data-color="strong"] {
|
|
104
|
-
background-color: var(--theme-base-default);
|
|
105
|
-
color: var(--theme-background-default);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.item[data-color="company"]:hover {
|
|
109
|
-
background-color: var(--company-background-subtle);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.item[data-color="company"][aria-selected="true"] {
|
|
113
|
-
background-color: var(--company-surface-default);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.media[data-color="company"] {
|
|
117
|
-
background-color: var(--company-surface-default);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.item[data-color="person"]:hover {
|
|
121
|
-
background-color: var(--person-background-subtle);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.item[data-color="person"][aria-selected="true"] {
|
|
125
|
-
background-color: var(--person-surface-default);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.media[data-color="person"] {
|
|
129
|
-
background-color: var(--person-surface-default);
|
|
130
|
-
}
|