@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.
- package/.github/workflows/ci-cd-main.yml +44 -0
- package/.github/workflows/ci-cd-pull-request.yml +39 -0
- package/.node-version +1 -0
- package/.storybook/main.ts +22 -0
- package/.storybook/preview.ts +15 -0
- package/CHANGELOG.md +13 -0
- package/README.md +2 -0
- package/biome.jsonc +65 -0
- package/lib/components/Avatar/Avatar.tsx +91 -0
- package/lib/components/Avatar/AvatarGroup.stories.ts +67 -0
- package/lib/components/Avatar/AvatarGroup.tsx +42 -0
- package/lib/components/Avatar/avatar.module.css +59 -0
- package/lib/components/Avatar/avatar.stories.tsx +44 -0
- package/lib/components/Avatar/avatarGroup.module.css +78 -0
- package/lib/components/Avatar/color.ts +71 -0
- package/lib/components/Avatar/index.ts +2 -0
- package/lib/components/Badge/Badge.tsx +19 -0
- package/lib/components/Badge/badge.module.css +36 -0
- package/lib/components/Badge/index.tsx +1 -0
- package/lib/components/Button/Button.stories.ts +44 -0
- package/lib/components/Button/Button.tsx +39 -0
- package/lib/components/Button/ButtonBase.tsx +53 -0
- package/lib/components/Button/ComboButton.stories.ts +45 -0
- package/lib/components/Button/ComboButton.tsx +44 -0
- package/lib/components/Button/button.module.css +82 -0
- package/lib/components/Button/buttonBase.module.css +77 -0
- package/lib/components/Button/comboButton.module.css +83 -0
- package/lib/components/Button/index.ts +3 -0
- package/lib/components/Header/DigdirLogomark.tsx +23 -0
- package/lib/components/Header/GlobalMenu.stories.tsx +202 -0
- package/lib/components/Header/GlobalMenu.tsx +131 -0
- package/lib/components/Header/Header.stories.ts +85 -0
- package/lib/components/Header/Header.tsx +64 -0
- package/lib/components/Header/HeaderBase.tsx +10 -0
- package/lib/components/Header/HeaderButton.stories.ts +54 -0
- package/lib/components/Header/HeaderButton.tsx +55 -0
- package/lib/components/Header/HeaderLogo.stories.ts +17 -0
- package/lib/components/Header/HeaderLogo.tsx +22 -0
- package/lib/components/Header/HeaderSearch.stories.ts +20 -0
- package/lib/components/Header/HeaderSearch.tsx +44 -0
- package/lib/components/Header/globalMenu.module.css +28 -0
- package/lib/components/Header/header.module.css +39 -0
- package/lib/components/Header/headerButton.module.css +35 -0
- package/lib/components/Header/headerLogo.module.css +24 -0
- package/lib/components/Header/headerSearch.module.css +30 -0
- package/lib/components/Header/index.tsx +5 -0
- package/lib/components/Icon/CheckboxIcon.stories.ts +25 -0
- package/lib/components/Icon/CheckboxIcon.tsx +29 -0
- package/lib/components/Icon/Icon.stories.ts +24 -0
- package/lib/components/Icon/Icon.tsx +23 -0
- package/lib/components/Icon/RadioIcon.stories.ts +25 -0
- package/lib/components/Icon/RadioIcon.tsx +29 -0
- package/lib/components/Icon/SvgIcon.tsx +18 -0
- package/lib/components/Icon/__AkselIcon.tsx +37 -0
- package/lib/components/Icon/checkboxIcon.module.css +21 -0
- package/lib/components/Icon/icon.module.css +4 -0
- package/lib/components/Icon/iconsMap.tsx +2078 -0
- package/lib/components/Icon/index.ts +5 -0
- package/lib/components/Icon/radioIcon.module.css +21 -0
- package/lib/components/Layout/Layout.stories.ts +127 -0
- package/lib/components/Layout/Layout.tsx +40 -0
- package/lib/components/Layout/LayoutBase.stories.ts +17 -0
- package/lib/components/Layout/LayoutBase.tsx +30 -0
- package/lib/components/Layout/LayoutBody.stories.ts +17 -0
- package/lib/components/Layout/LayoutBody.tsx +16 -0
- package/lib/components/Layout/LayoutContent.stories.ts +17 -0
- package/lib/components/Layout/LayoutContent.tsx +15 -0
- package/lib/components/Layout/LayoutSidebar.stories.ts +17 -0
- package/lib/components/Layout/LayoutSidebar.tsx +16 -0
- package/lib/components/Layout/index.tsx +4 -0
- package/lib/components/Layout/layout.module.css +63 -0
- package/lib/components/Menu/Menu.stories.ts +495 -0
- package/lib/components/Menu/Menu.tsx +123 -0
- package/lib/components/Menu/MenuBase.tsx +17 -0
- package/lib/components/Menu/MenuGroup.tsx +18 -0
- package/lib/components/Menu/MenuHeader.tsx +13 -0
- package/lib/components/Menu/MenuItem.stories.ts +127 -0
- package/lib/components/Menu/MenuItem.tsx +58 -0
- package/lib/components/Menu/MenuItemBase.tsx +62 -0
- package/lib/components/Menu/MenuItemLabel.tsx +30 -0
- package/lib/components/Menu/MenuItemMedia.tsx +42 -0
- package/lib/components/Menu/MenuOption.stories.ts +50 -0
- package/lib/components/Menu/MenuOption.tsx +45 -0
- package/lib/components/Menu/MenuSearch.stories.ts +18 -0
- package/lib/components/Menu/MenuSearch.tsx +25 -0
- package/lib/components/Menu/index.ts +10 -0
- package/lib/components/Menu/menu.module.css +26 -0
- package/lib/components/Menu/menuHeader.module.css +12 -0
- package/lib/components/Menu/menuItem.module.css +136 -0
- package/lib/components/Menu/menuOption.module.css +29 -0
- package/lib/components/Menu/menuSearch.module.css +29 -0
- package/lib/components/Menu/useClickOutside.ts +21 -0
- package/lib/components/Menu/useEscapeKey.ts +16 -0
- package/lib/components/Toolbar/Toolbar.stories.tsx +188 -0
- package/lib/components/Toolbar/Toolbar.tsx +138 -0
- package/lib/components/Toolbar/ToolbarAdd.stories.ts +25 -0
- package/lib/components/Toolbar/ToolbarAdd.tsx +25 -0
- package/lib/components/Toolbar/ToolbarBase.tsx +27 -0
- package/lib/components/Toolbar/ToolbarButton.stories.ts +32 -0
- package/lib/components/Toolbar/ToolbarButton.tsx +65 -0
- package/lib/components/Toolbar/ToolbarFilter.stories.ts +66 -0
- package/lib/components/Toolbar/ToolbarFilter.tsx +70 -0
- package/lib/components/Toolbar/ToolbarMenu.stories.ts +37 -0
- package/lib/components/Toolbar/ToolbarMenu.tsx +28 -0
- package/lib/components/Toolbar/ToolbarOptions.stories.ts +108 -0
- package/lib/components/Toolbar/ToolbarOptions.tsx +61 -0
- package/lib/components/Toolbar/ToolbarSearch.stories.ts +19 -0
- package/lib/components/Toolbar/ToolbarSearch.tsx +24 -0
- package/lib/components/Toolbar/index.js +3 -0
- package/lib/components/Toolbar/toolbar.module.css +43 -0
- package/lib/components/Toolbar/toolbarButton.module.css +3 -0
- package/lib/components/Toolbar/toolbarSearch.module.css +28 -0
- package/lib/components/index.ts +1 -0
- package/lib/css/colors.css +113 -0
- package/lib/css/global.css +12 -0
- package/lib/css/theme-company.css +15 -0
- package/lib/css/theme-global.css +15 -0
- package/lib/css/theme-neutral.css +15 -0
- package/lib/css/theme-person.css +15 -0
- package/lib/css/theme.css +24 -0
- package/lib/index.ts +1 -0
- package/package.json +52 -0
- package/tsconfig.json +23 -0
- package/tsconfig.node.json +11 -0
- package/typings.d.ts +1 -0
- package/vite.config.ts +20 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { fn } from '@storybook/test';
|
|
3
|
+
|
|
4
|
+
import { MenuItem } from './MenuItem';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Menu/MenuItem',
|
|
8
|
+
component: MenuItem,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: {},
|
|
11
|
+
args: {},
|
|
12
|
+
} satisfies Meta<typeof MenuItem>;
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
icon: 'inbox',
|
|
20
|
+
title: 'Innboks',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const DefaultBadge: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
icon: 'inbox',
|
|
27
|
+
title: 'Innboks',
|
|
28
|
+
badge: 4,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Large: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
size: 'lg',
|
|
35
|
+
icon: 'inbox',
|
|
36
|
+
title: 'Innboks',
|
|
37
|
+
color: 'strong',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const LargeBadge: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
size: 'lg',
|
|
44
|
+
icon: 'inbox',
|
|
45
|
+
title: 'Innboks',
|
|
46
|
+
badge: 4,
|
|
47
|
+
color: 'strong',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Person: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
avatar: {
|
|
54
|
+
type: 'person',
|
|
55
|
+
name: 'Eirik Horneland',
|
|
56
|
+
},
|
|
57
|
+
title: 'Eirik Horneland',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const PersonGroup: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
avatarGroup: {
|
|
64
|
+
items: [
|
|
65
|
+
{
|
|
66
|
+
type: 'person',
|
|
67
|
+
name: 'Eirik Horneland',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'person',
|
|
71
|
+
name: 'Japhet Sery Larsen',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'person',
|
|
75
|
+
name: 'Aune Heggebø',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
title: '3 personer',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const Company: Story = {
|
|
84
|
+
args: {
|
|
85
|
+
avatar: {
|
|
86
|
+
type: 'company',
|
|
87
|
+
name: 'Sportsklubben Brann',
|
|
88
|
+
},
|
|
89
|
+
title: 'Sportsklubben Brann',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const CompanyGroup: Story = {
|
|
94
|
+
args: {
|
|
95
|
+
avatarGroup: {
|
|
96
|
+
type: 'company',
|
|
97
|
+
items: [
|
|
98
|
+
{
|
|
99
|
+
name: 'Sportsklubben Brann',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'Bergen Næringsråd',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'Bergen bar',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
title: '3 virksomheter',
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const PersonAccount: Story = {
|
|
114
|
+
args: {
|
|
115
|
+
...Person.args,
|
|
116
|
+
size: 'lg',
|
|
117
|
+
description: 'Født: XX.XX.XXXX',
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const CompanyAccount: Story = {
|
|
122
|
+
args: {
|
|
123
|
+
...Company.args,
|
|
124
|
+
size: 'lg',
|
|
125
|
+
description: 'Org nr.: XX.XX.XXXX',
|
|
126
|
+
},
|
|
127
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ElementType, ReactNode } from 'react';
|
|
2
|
+
import type { AvatarGroupProps, AvatarProps } from '../Avatar';
|
|
3
|
+
import type { IconName } from '../Icon';
|
|
4
|
+
import { MenuItemBase, type MenuItemColor, type MenuItemSize } from './MenuItemBase';
|
|
5
|
+
import { MenuItemLabel } from './MenuItemLabel';
|
|
6
|
+
import { MenuItemMedia } from './MenuItemMedia';
|
|
7
|
+
|
|
8
|
+
export interface MenuItemProps {
|
|
9
|
+
id: string;
|
|
10
|
+
type?: string;
|
|
11
|
+
as?: ElementType;
|
|
12
|
+
color?: MenuItemColor;
|
|
13
|
+
href?: string;
|
|
14
|
+
onClick?: () => void;
|
|
15
|
+
hidden?: boolean;
|
|
16
|
+
collapsible?: boolean;
|
|
17
|
+
expanded?: boolean;
|
|
18
|
+
selected?: boolean;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
group?: string | number;
|
|
21
|
+
size?: MenuItemSize;
|
|
22
|
+
title?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
label?: string;
|
|
25
|
+
badge?: string;
|
|
26
|
+
icon?: IconName;
|
|
27
|
+
avatar?: AvatarProps;
|
|
28
|
+
avatarGroup?: AvatarGroupProps;
|
|
29
|
+
children?: ReactNode;
|
|
30
|
+
items?: MenuItemProps[];
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const MenuItem = ({
|
|
35
|
+
as = 'a',
|
|
36
|
+
color,
|
|
37
|
+
children,
|
|
38
|
+
selected,
|
|
39
|
+
disabled,
|
|
40
|
+
size = 'sm',
|
|
41
|
+
icon,
|
|
42
|
+
avatar,
|
|
43
|
+
avatarGroup,
|
|
44
|
+
badge,
|
|
45
|
+
label,
|
|
46
|
+
title,
|
|
47
|
+
description,
|
|
48
|
+
...rest
|
|
49
|
+
}: MenuItemProps) => {
|
|
50
|
+
return (
|
|
51
|
+
<MenuItemBase as={as} size={size} badge={badge} color={color} selected={selected} disabled={disabled} {...rest}>
|
|
52
|
+
<MenuItemMedia color={color} size={size} icon={icon} avatar={avatar} avatarGroup={avatarGroup} />
|
|
53
|
+
<MenuItemLabel title={title} description={description} size={size}>
|
|
54
|
+
{label || children}
|
|
55
|
+
</MenuItemLabel>
|
|
56
|
+
</MenuItemBase>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import cx from 'classnames';
|
|
2
|
+
import type { ElementType, ReactNode } from 'react';
|
|
3
|
+
import { Badge } from '../Badge';
|
|
4
|
+
import { Icon, type IconName } from '../Icon';
|
|
5
|
+
import styles from './menuItem.module.css';
|
|
6
|
+
|
|
7
|
+
export type MenuItemColor = 'default' | 'subtle' | 'strong' | 'company' | 'person';
|
|
8
|
+
export type MenuItemSize = 'sm' | 'md' | 'lg';
|
|
9
|
+
|
|
10
|
+
export interface MenuItemBaseProps {
|
|
11
|
+
as?: ElementType;
|
|
12
|
+
color?: MenuItemColor;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
size?: MenuItemSize;
|
|
15
|
+
linkIcon?: IconName;
|
|
16
|
+
badge?: string | number | undefined;
|
|
17
|
+
collapsible?: boolean;
|
|
18
|
+
expanded?: boolean;
|
|
19
|
+
selected?: boolean;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const MenuItemBase = ({
|
|
25
|
+
as,
|
|
26
|
+
size,
|
|
27
|
+
color,
|
|
28
|
+
linkIcon,
|
|
29
|
+
badge,
|
|
30
|
+
collapsible = false,
|
|
31
|
+
expanded = false,
|
|
32
|
+
selected = false,
|
|
33
|
+
disabled = false,
|
|
34
|
+
className,
|
|
35
|
+
children,
|
|
36
|
+
...rest
|
|
37
|
+
}: MenuItemBaseProps) => {
|
|
38
|
+
const Component = as || 'a';
|
|
39
|
+
|
|
40
|
+
const applicableIcon =
|
|
41
|
+
collapsible && expanded ? 'chevron-up' : collapsible ? 'chevron-down' : linkIcon ? 'chevron-right' : linkIcon;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Component
|
|
45
|
+
data-size={size}
|
|
46
|
+
data-color={color}
|
|
47
|
+
aria-expanded={expanded}
|
|
48
|
+
aria-disabled={disabled}
|
|
49
|
+
aria-selected={selected}
|
|
50
|
+
className={cx(styles?.item, className)}
|
|
51
|
+
{...rest}
|
|
52
|
+
>
|
|
53
|
+
<div className={styles.content}>
|
|
54
|
+
{children}
|
|
55
|
+
{badge && <Badge>{badge}</Badge>}
|
|
56
|
+
</div>
|
|
57
|
+
<div className={styles?.action}>
|
|
58
|
+
{applicableIcon && <Icon name={applicableIcon} className={styles?.actionIcon} />}
|
|
59
|
+
</div>
|
|
60
|
+
</Component>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { MenuItemSize } from './MenuItemBase';
|
|
3
|
+
import styles from './menuItem.module.css';
|
|
4
|
+
|
|
5
|
+
export interface MenuItemLabelProps {
|
|
6
|
+
size?: MenuItemSize;
|
|
7
|
+
label?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const MenuItemLabel = ({ size = 'sm', label, title, description, children }: MenuItemLabelProps) => {
|
|
14
|
+
return (
|
|
15
|
+
<span className={styles?.label} data-size={size}>
|
|
16
|
+
{children ? (
|
|
17
|
+
children
|
|
18
|
+
) : (
|
|
19
|
+
<>
|
|
20
|
+
<strong className={styles?.title} data-size={size}>
|
|
21
|
+
{title || label}
|
|
22
|
+
</strong>
|
|
23
|
+
<span className={styles?.description} data-size={size}>
|
|
24
|
+
{description}
|
|
25
|
+
</span>
|
|
26
|
+
</>
|
|
27
|
+
)}
|
|
28
|
+
</span>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, type AvatarSize } from '../Avatar';
|
|
3
|
+
import { Icon, type IconName } from '../Icon';
|
|
4
|
+
import type { MenuItemColor, MenuItemSize } from './MenuItemBase';
|
|
5
|
+
import styles from './menuItem.module.css';
|
|
6
|
+
|
|
7
|
+
interface MenuItemMediaProps {
|
|
8
|
+
color?: MenuItemColor;
|
|
9
|
+
size?: MenuItemSize;
|
|
10
|
+
icon?: IconName;
|
|
11
|
+
avatar?: AvatarProps;
|
|
12
|
+
avatarGroup?: AvatarGroupProps;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sizeMap = {
|
|
17
|
+
avatar: {
|
|
18
|
+
sm: 'sm',
|
|
19
|
+
md: 'md',
|
|
20
|
+
lg: 'lg',
|
|
21
|
+
},
|
|
22
|
+
avatarGroup: {
|
|
23
|
+
sm: 'xs',
|
|
24
|
+
md: 'sm',
|
|
25
|
+
lg: 'md',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const MenuItemMedia = ({ size = 'sm', color, icon, avatar, avatarGroup, children }: MenuItemMediaProps) => {
|
|
30
|
+
if (!icon && !avatar && !avatarGroup && !children) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className={styles.media} data-size={size} data-color={!icon ? null : color}>
|
|
36
|
+
{icon ? <Icon name={icon} variant={color === 'strong' ? 'solid' : 'outline'} className={styles?.icon} /> : ''}
|
|
37
|
+
{avatar && <Avatar {...avatar} size={sizeMap?.avatar[size] as AvatarSize} />}
|
|
38
|
+
{avatarGroup && <AvatarGroup {...avatarGroup} size={sizeMap?.avatarGroup[size] as AvatarSize} />}
|
|
39
|
+
{children}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { MenuOption } from './MenuOption';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Menu/MenuOption',
|
|
7
|
+
component: MenuOption,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {},
|
|
10
|
+
args: {},
|
|
11
|
+
} satisfies Meta<typeof MenuOption>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Checkbox: Story = {
|
|
17
|
+
args: {
|
|
18
|
+
type: 'checkbox',
|
|
19
|
+
title: 'Title',
|
|
20
|
+
size: 'sm',
|
|
21
|
+
checked: false,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const CheckboxChecked: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
type: 'checkbox',
|
|
28
|
+
title: 'Title',
|
|
29
|
+
size: 'sm',
|
|
30
|
+
checked: true,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Radio: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
type: 'radio',
|
|
37
|
+
title: 'Title',
|
|
38
|
+
size: 'sm',
|
|
39
|
+
checked: false,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const RadioChecked: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
type: 'radio',
|
|
46
|
+
title: 'Title',
|
|
47
|
+
size: 'sm',
|
|
48
|
+
checked: true,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ChangeEventHandler } from 'react';
|
|
2
|
+
import { CheckboxIcon, RadioIcon } from '../Icon';
|
|
3
|
+
import { MenuItemBase, type MenuItemSize } from './MenuItemBase';
|
|
4
|
+
import { MenuItemLabel } from './MenuItemLabel';
|
|
5
|
+
import styles from './menuOption.module.css';
|
|
6
|
+
|
|
7
|
+
export type MenuOptionType = 'checkbox' | 'radio';
|
|
8
|
+
|
|
9
|
+
export interface MenuOptionProps {
|
|
10
|
+
value: string | number;
|
|
11
|
+
label: string;
|
|
12
|
+
group?: string;
|
|
13
|
+
size?: MenuItemSize;
|
|
14
|
+
name?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
checked?: boolean;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
onChange?: ChangeEventHandler;
|
|
20
|
+
type?: MenuOptionType;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const MenuOption = ({
|
|
24
|
+
size = 'sm',
|
|
25
|
+
type,
|
|
26
|
+
name,
|
|
27
|
+
value,
|
|
28
|
+
label,
|
|
29
|
+
title,
|
|
30
|
+
description,
|
|
31
|
+
checked = false,
|
|
32
|
+
disabled,
|
|
33
|
+
onChange,
|
|
34
|
+
}: MenuOptionProps) => {
|
|
35
|
+
return (
|
|
36
|
+
<MenuItemBase className={styles?.label} disabled={disabled} selected={checked} size={size} as="label">
|
|
37
|
+
<input className={styles?.input} name={name} value={value} type={type} checked={checked} onChange={onChange} />
|
|
38
|
+
{type === 'checkbox' && <CheckboxIcon checked={checked} className={styles.icon} />}
|
|
39
|
+
{type === 'radio' && <RadioIcon checked={checked} className={styles.icon} />}
|
|
40
|
+
<MenuItemLabel title={title} description={description} size={size}>
|
|
41
|
+
{label}
|
|
42
|
+
</MenuItemLabel>
|
|
43
|
+
</MenuItemBase>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { MenuSearch } from './MenuSearch';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Menu/MenuSearch',
|
|
7
|
+
component: MenuSearch,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {},
|
|
10
|
+
args: {},
|
|
11
|
+
} satisfies Meta<typeof MenuSearch>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
args: {},
|
|
18
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ChangeEventHandler } from 'react';
|
|
2
|
+
import styles from './menuSearch.module.css';
|
|
3
|
+
|
|
4
|
+
export interface MenuSearchProps {
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
name: string;
|
|
7
|
+
value: string;
|
|
8
|
+
onChange: ChangeEventHandler;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const MenuSearch = ({ value, name, placeholder = 'Søk', onChange }: MenuSearchProps) => {
|
|
12
|
+
return (
|
|
13
|
+
<div className={styles.field}>
|
|
14
|
+
<input
|
|
15
|
+
type="search"
|
|
16
|
+
value={value}
|
|
17
|
+
name={name}
|
|
18
|
+
placeholder={placeholder}
|
|
19
|
+
className={styles.input}
|
|
20
|
+
onChange={onChange}
|
|
21
|
+
autoComplete="off"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './MenuItemBase';
|
|
2
|
+
export * from './MenuItemLabel';
|
|
3
|
+
export * from './MenuItemMedia';
|
|
4
|
+
export * from './MenuItem';
|
|
5
|
+
export * from './MenuOption';
|
|
6
|
+
export * from './MenuSearch';
|
|
7
|
+
export * from './MenuGroup';
|
|
8
|
+
export * from './MenuHeader';
|
|
9
|
+
export * from './MenuBase';
|
|
10
|
+
export * from './Menu';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.menu {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.menu ul {
|
|
7
|
+
list-style: none;
|
|
8
|
+
padding: 0;
|
|
9
|
+
margin: 0;
|
|
10
|
+
text-indent: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.menu li {
|
|
14
|
+
list-style: none;
|
|
15
|
+
list-style-type: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.group[data-divider="true"] + .group {
|
|
19
|
+
border-top: 1px solid;
|
|
20
|
+
border-color: var(--theme-border-subtle);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.item[aria-expanded="true"] > .group {
|
|
24
|
+
border-top: 1px solid;
|
|
25
|
+
border-color: var(--theme-border-subtle);
|
|
26
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
/* colors */
|
|
91
|
+
|
|
92
|
+
.item:hover {
|
|
93
|
+
background-color: var(--theme-surface-default);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.item[aria-selected="true"] {
|
|
97
|
+
background-color: var(--theme-background-default);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.media[data-color="subtle"] {
|
|
101
|
+
background-color: var(--theme-background-default);
|
|
102
|
+
color: var(--theme-text-default);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.media[data-color="strong"] {
|
|
106
|
+
background-color: var(--theme-base-default);
|
|
107
|
+
color: var(--theme-background-default);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* company */
|
|
111
|
+
|
|
112
|
+
.item[data-color="company"]:hover {
|
|
113
|
+
background-color: var(--company-background-subtle);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.item[data-color="company"][aria-selected="true"] {
|
|
117
|
+
background-color: var(--company-surface-default);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.media[data-color="company"] {
|
|
121
|
+
background-color: var(--company-surface-default);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* person */
|
|
125
|
+
|
|
126
|
+
.item[data-color="person"]:hover {
|
|
127
|
+
background-color: var(--person-background-subtle);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.item[data-color="person"][aria-selected="true"] {
|
|
131
|
+
background-color: var(--person-surface-default);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.media[data-color="person"] {
|
|
135
|
+
background-color: var(--person-surface-default);
|
|
136
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.label {
|
|
2
|
+
padding-left: 6px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.input {
|
|
6
|
+
position: absolute;
|
|
7
|
+
opacity: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.icon {
|
|
11
|
+
font-size: 1rem;
|
|
12
|
+
border: 2px solid;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.icon > svg {
|
|
19
|
+
color: transparent;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.label:hover .icon > svg {
|
|
23
|
+
color: var(--theme-base-default);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.icon[data-checked="true"] > svg,
|
|
27
|
+
.label:hover .icon[data-checked="true"] > svg {
|
|
28
|
+
color: var(--theme-background-subtle);
|
|
29
|
+
}
|