@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,29 @@
1
+ .input {
2
+ width: 100%;
3
+ font-size: 0.875rem;
4
+ line-height: 1rem;
5
+ font-weight: normal;
6
+ padding: 9px;
7
+ border-radius: 2px;
8
+ border: 1px solid;
9
+ border-color: var(--theme-border-default);
10
+ }
11
+
12
+ .button[aria-selected="true"] {
13
+ background-color: var(--theme-background-subtle);
14
+ color: var(--theme-text-default);
15
+ }
16
+
17
+ .close {
18
+ padding: 6px 0;
19
+ }
20
+
21
+ .icon {
22
+ border-left: 1px solid;
23
+ border-color: var(--theme-background-subtle);
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ font-size: 1.25rem;
28
+ padding: 0 6px;
29
+ }
@@ -0,0 +1,21 @@
1
+ import { type RefObject, useEffect } from 'react';
2
+
3
+ export const useClickOutside = (ref: RefObject<HTMLDivElement>, callback: () => void) => {
4
+ useEffect(() => {
5
+ const handleClickOutside = (event: MouseEvent | TouchEvent) => {
6
+ if (ref.current && !ref.current.contains(event.target as Node)) {
7
+ callback();
8
+ }
9
+ };
10
+
11
+ document.addEventListener('mouseup', handleClickOutside);
12
+ document.addEventListener('touchend', handleClickOutside);
13
+
14
+ return () => {
15
+ document.removeEventListener('mouseup', handleClickOutside);
16
+ document.removeEventListener('touchend', handleClickOutside);
17
+ };
18
+ }, [callback, ref]);
19
+
20
+ return ref;
21
+ };
@@ -0,0 +1,16 @@
1
+ import { useEffect } from 'react';
2
+
3
+ export const useEscapeKey = (onEscape: () => void): void => {
4
+ useEffect(() => {
5
+ const handleEscape = (event: KeyboardEvent) => {
6
+ if (event.key === 'Escape') {
7
+ onEscape();
8
+ }
9
+ };
10
+ document.addEventListener('keydown', handleEscape);
11
+
12
+ return () => {
13
+ document.removeEventListener('keydown', handleEscape);
14
+ };
15
+ }, [onEscape]);
16
+ };
@@ -0,0 +1,188 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+ import { type FilterState, Toolbar } from './Toolbar';
4
+
5
+ const meta = {
6
+ title: 'Toolbar/Toolbar',
7
+ component: Toolbar,
8
+ tags: ['autodocs'],
9
+ parameters: {},
10
+ args: {},
11
+ } satisfies Meta<typeof Toolbar>;
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof meta>;
15
+
16
+ export const Default: Story = {
17
+ args: {
18
+ menu: {
19
+ label: 'Ola Nordmann',
20
+ value: 'ola',
21
+ items: [
22
+ {
23
+ avatar: {
24
+ name: 'Ola Nordmann',
25
+ },
26
+ label: 'Ola Nordmann',
27
+ },
28
+ {
29
+ avatar: {
30
+ name: 'Kari Nordmann',
31
+ },
32
+ label: 'Kari Nordmann',
33
+ },
34
+ ],
35
+ },
36
+ filters: [
37
+ {
38
+ removable: true,
39
+ name: 'from',
40
+ optionType: 'checkbox',
41
+ label: 'Velg avsender',
42
+ options: [
43
+ {
44
+ value: 'skatt',
45
+ label: 'Skatteetaten',
46
+ },
47
+ {
48
+ value: 'brreg',
49
+ label: 'Brønnøysund',
50
+ },
51
+ {
52
+ value: 'nav',
53
+ label: 'NAV',
54
+ },
55
+ {
56
+ value: 'oslo',
57
+ label: 'Oslo kommune',
58
+ },
59
+ ],
60
+ },
61
+ {
62
+ removable: true,
63
+ name: 'to',
64
+ optionType: 'radio',
65
+ label: 'Velg mottaker',
66
+ options: [
67
+ {
68
+ value: 'ola',
69
+ label: 'Ola Nordmann',
70
+ },
71
+ {
72
+ value: 'kari',
73
+ label: 'Kari Nordmann',
74
+ },
75
+ ],
76
+ },
77
+ {
78
+ removable: true,
79
+ name: 'status',
80
+ optionType: 'radio',
81
+ label: 'Velg status',
82
+ options: [
83
+ {
84
+ group: '1',
85
+ value: 'draft',
86
+ label: 'Utkast',
87
+ },
88
+ {
89
+ group: '1',
90
+ value: 'sent',
91
+ label: 'Sendt',
92
+ },
93
+ {
94
+ group: '2',
95
+ value: 'in-progress',
96
+ label: 'Under arbeid',
97
+ },
98
+ {
99
+ group: '2',
100
+ value: 'requires-attention',
101
+ label: 'Krever handling',
102
+ },
103
+ {
104
+ group: '2',
105
+ value: 'completed',
106
+ label: 'Avsluttet',
107
+ },
108
+ ],
109
+ },
110
+ ],
111
+ },
112
+ };
113
+
114
+ export const CustomFilterLabel: Story = {
115
+ args: {
116
+ ...Default.args,
117
+ getFilterLabel: (name, value) => {
118
+ return Array.isArray(value) && value.length > 1 ? `${value.length} selected` : value;
119
+ },
120
+ },
121
+ };
122
+
123
+ export const NoFilters: Story = {
124
+ args: {
125
+ ...Default.args,
126
+ filters: [],
127
+ },
128
+ };
129
+
130
+ export const StaticFilters: Story = {
131
+ args: {
132
+ filters: Default?.args?.filters?.map((item) => {
133
+ return {
134
+ ...item,
135
+ removable: false,
136
+ };
137
+ }),
138
+ },
139
+ };
140
+
141
+ export const ControlledStateFilters = () => {
142
+ const [filterState, setFilterState] = React.useState<FilterState>({
143
+ from: ['skatt', 'brreg'],
144
+ });
145
+ return <Toolbar filters={Default.args!.filters} filterState={filterState} onFilterStateChange={setFilterState} />;
146
+ };
147
+
148
+ export const FilterAndSearch: Story = {
149
+ args: {
150
+ search: {
151
+ placeholder: 'Søk etter filter',
152
+ },
153
+ filters: [
154
+ {
155
+ name: 'status',
156
+ optionType: 'checkbox',
157
+ label: 'Velg status',
158
+ options: [
159
+ {
160
+ group: '1',
161
+ value: 'draft',
162
+ label: 'Utkast',
163
+ },
164
+ {
165
+ group: '1',
166
+ value: 'sent',
167
+ label: 'Sendt',
168
+ },
169
+ {
170
+ group: '2',
171
+ value: 'in-progress',
172
+ label: 'Under arbeid',
173
+ },
174
+ {
175
+ group: '2',
176
+ value: 'requires-attention',
177
+ label: 'Krever handling',
178
+ },
179
+ {
180
+ group: '2',
181
+ value: 'completed',
182
+ label: 'Avsluttet',
183
+ },
184
+ ],
185
+ },
186
+ ],
187
+ },
188
+ };
@@ -0,0 +1,138 @@
1
+ 'use client';
2
+ import { useMemo, useState } from 'react';
3
+ import { ToolbarAdd } from './ToolbarAdd';
4
+ import { ToolbarBase } from './ToolbarBase';
5
+ import { ToolbarFilter, type ToolbarFilterProps } from './ToolbarFilter.tsx';
6
+ import { ToolbarMenu, type ToolbarMenuProps } from './ToolbarMenu.tsx';
7
+ import type { ToolbarOptionType } from './ToolbarOptions.tsx';
8
+ import { ToolbarSearch, type ToolbarSearchProps } from './ToolbarSearch.tsx';
9
+
10
+ export type FilterState = Record<string, ToolbarFilterProps['value']>;
11
+ type ExpandedItemType = 'filter' | 'menu' | 'add-filter';
12
+
13
+ type ExpandedItem = {
14
+ name: string;
15
+ type: ExpandedItemType;
16
+ } | null;
17
+
18
+ export interface ToolbarProps {
19
+ filters?: ToolbarFilterProps[];
20
+ search?: ToolbarSearchProps;
21
+ menu?: ToolbarMenuProps;
22
+ filterState?: FilterState;
23
+ getFilterLabel?: (name: string, value: ToolbarFilterProps['value']) => string;
24
+ onFilterStateChange?: (state: FilterState) => void;
25
+ }
26
+
27
+ export const Toolbar = ({
28
+ filters = [],
29
+ filterState,
30
+ onFilterStateChange,
31
+ search,
32
+ menu,
33
+ getFilterLabel,
34
+ }: ToolbarProps) => {
35
+ const [expandedItem, setExpandedItem] = useState<ExpandedItem>(null);
36
+ const [localFilterState, setLocalFilterState] = useState<Record<string, ToolbarFilterProps['value']>>(
37
+ filterState ?? {},
38
+ );
39
+ const changeFilterState = typeof onFilterStateChange === 'function' ? onFilterStateChange : setLocalFilterState;
40
+ const applicableFilterState = filterState || localFilterState;
41
+ const [hiddenFilterNames, setHiddenFilterNames] = useState<string[]>(
42
+ filters
43
+ ?.filter((item) => item.removable && typeof applicableFilterState[item.name] === 'undefined')
44
+ .map((item) => item.name) ?? [],
45
+ );
46
+
47
+ const visibleFilters = useMemo(
48
+ () => filters.filter((item) => !hiddenFilterNames.includes(item.name)),
49
+ [filters, hiddenFilterNames],
50
+ );
51
+ const hiddenFilters = useMemo(
52
+ () => filters.filter((item) => hiddenFilterNames.includes(item.name)),
53
+ [filters, hiddenFilterNames],
54
+ );
55
+
56
+ const onToggle = (type: ExpandedItemType, name: string) => {
57
+ if (expandedItem?.name === name && expandedItem.type === type) {
58
+ setExpandedItem(null);
59
+ } else {
60
+ setExpandedItem({ name, type });
61
+ }
62
+ };
63
+
64
+ const onFilterChange = (name: string, value: ToolbarFilterProps['value'], optionType: ToolbarOptionType) => {
65
+ if (optionType === 'radio') {
66
+ changeFilterState({
67
+ ...applicableFilterState,
68
+ [name]: value,
69
+ });
70
+ onToggle('filter', name);
71
+ } else {
72
+ changeFilterState({
73
+ ...applicableFilterState,
74
+ [name]: applicableFilterState[name]
75
+ ? applicableFilterState[name].some((v) => value?.includes(v))
76
+ ? applicableFilterState[name].filter((v) => !(value || []).includes(v))
77
+ : [...applicableFilterState[name], ...(value || [])]
78
+ : value,
79
+ });
80
+ }
81
+ };
82
+
83
+ const onFilterRemove = (name: string) => {
84
+ setHiddenFilterNames((prevState) => [...prevState, name]);
85
+ changeFilterState({
86
+ ...applicableFilterState,
87
+ [name]: undefined,
88
+ });
89
+ };
90
+
91
+ const onFilterAdd = (name: string) => {
92
+ onToggle('filter', name);
93
+ setHiddenFilterNames((prevState) => prevState.filter((prevName) => prevName !== name));
94
+ };
95
+
96
+ return (
97
+ <ToolbarBase onClose={() => setExpandedItem(null)}>
98
+ {menu && <ToolbarMenu onToggle={() => onToggle('menu', '')} expanded={expandedItem?.type === 'menu'} {...menu} />}
99
+ {visibleFilters.map((item) => {
100
+ return (
101
+ <ToolbarFilter
102
+ key={item.name}
103
+ onToggle={() => onToggle('filter', item.name)}
104
+ expanded={item.name === expandedItem?.name && expandedItem?.type === 'filter'}
105
+ onRemove={() => {
106
+ onFilterRemove(item.name);
107
+ }}
108
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
109
+ onFilterChange(item.name, [event.target.value], item.optionType);
110
+ }}
111
+ name={item.name}
112
+ options={item.options}
113
+ label={item.label}
114
+ value={applicableFilterState[item.name]}
115
+ optionType={item.optionType}
116
+ removable={item.removable}
117
+ getSelectedLabel={getFilterLabel}
118
+ />
119
+ );
120
+ })}
121
+ {hiddenFilters?.length > 0 && (
122
+ <ToolbarAdd
123
+ expanded={expandedItem?.type === 'add-filter'}
124
+ onToggle={() => onToggle('add-filter', '')}
125
+ items={hiddenFilters.map((item) => ({
126
+ id: item.name,
127
+ label: item.label,
128
+ name: item.name,
129
+ onClick: () => {
130
+ onFilterAdd(item.name);
131
+ },
132
+ }))}
133
+ />
134
+ )}
135
+ {search && <ToolbarSearch {...search} />}
136
+ </ToolbarBase>
137
+ );
138
+ };
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { fn } from '@storybook/test';
3
+
4
+ import { ToolbarAdd } from './ToolbarAdd';
5
+
6
+ const meta = {
7
+ title: 'Toolbar/ToolbarAdd',
8
+ component: ToolbarAdd,
9
+ tags: ['autodocs'],
10
+ parameters: {},
11
+ args: {},
12
+ } satisfies Meta<typeof ToolbarAdd>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = {
18
+ args: {
19
+ items: [
20
+ {
21
+ label: 'Velg dato',
22
+ },
23
+ ],
24
+ },
25
+ };
@@ -0,0 +1,25 @@
1
+ import type { MouseEventHandler } from 'react';
2
+ import { Menu, type MenuItemProps } from '../Menu';
3
+ import { ToolbarButton } from './ToolbarButton';
4
+ import styles from './toolbar.module.css';
5
+
6
+ export interface ToolbarAddProps {
7
+ items: MenuItemProps[];
8
+ expanded: boolean;
9
+ onToggle?: MouseEventHandler;
10
+ label?: string;
11
+ className?: string;
12
+ }
13
+
14
+ export const ToolbarAdd = ({ expanded = false, onToggle, label = 'Legg til', items }: ToolbarAddProps) => {
15
+ return (
16
+ <div className={styles.toggle}>
17
+ <ToolbarButton as="div" type="add" onToggle={onToggle}>
18
+ {label}
19
+ </ToolbarButton>
20
+ <div aria-expanded={expanded} className={styles?.dropdown}>
21
+ <Menu theme="global" items={items} />
22
+ </div>
23
+ </div>
24
+ );
25
+ };
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+ import { type ReactNode, useRef } from 'react';
3
+ import { useClickOutside } from '../Menu/useClickOutside.ts';
4
+ import { useEscapeKey } from '../Menu/useEscapeKey.ts';
5
+ import styles from './toolbar.module.css';
6
+ export interface ToolbarBaseProps {
7
+ children?: ReactNode;
8
+ onClose?: () => void;
9
+ }
10
+
11
+ export const ToolbarBase = ({ children, onClose }: ToolbarBaseProps) => {
12
+ const ref = useRef<HTMLDivElement>(null);
13
+
14
+ useClickOutside(ref, () => {
15
+ onClose?.();
16
+ });
17
+
18
+ useEscapeKey(() => {
19
+ onClose?.();
20
+ });
21
+
22
+ return (
23
+ <div className={styles.toolbar} ref={ref}>
24
+ {children}
25
+ </div>
26
+ );
27
+ };
@@ -0,0 +1,32 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { ToolbarButton } from './ToolbarButton';
4
+
5
+ const meta = {
6
+ title: 'Toolbar/ToolbarButton',
7
+ component: ToolbarButton,
8
+ tags: ['autodocs'],
9
+ parameters: {},
10
+ args: {
11
+ children: 'Label',
12
+ },
13
+ } satisfies Meta<typeof ToolbarButton>;
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+
18
+ export const Default: Story = {
19
+ args: {},
20
+ };
21
+
22
+ export const Add: Story = {
23
+ args: {
24
+ type: 'add',
25
+ },
26
+ };
27
+
28
+ export const Removable: Story = {
29
+ args: {
30
+ removable: true,
31
+ },
32
+ };
@@ -0,0 +1,65 @@
1
+ import type { ElementType, MouseEventHandler, ReactNode } from 'react';
2
+ import { Button, ComboButton } from '../Button';
3
+ import styles from './toolbarButton.module.css';
4
+
5
+ export type ToolbarButtonType = 'add' | 'select' | 'switch';
6
+
7
+ export interface ToolbarButtonProps {
8
+ as?: ElementType;
9
+ type?: ToolbarButtonType;
10
+ removable?: boolean;
11
+ selected?: boolean;
12
+ icon?: string;
13
+ active?: boolean;
14
+ children?: ReactNode;
15
+ onToggle?: MouseEventHandler;
16
+ onRemove?: MouseEventHandler;
17
+ }
18
+
19
+ export const ToolbarButton = ({
20
+ type = 'select',
21
+ selected = false,
22
+ removable = false,
23
+ active,
24
+ children,
25
+ onToggle,
26
+ onRemove,
27
+ }: ToolbarButtonProps) => {
28
+ if (removable) {
29
+ return (
30
+ <ComboButton
31
+ className={styles.remove}
32
+ variant={active ? 'solid' : 'outline'}
33
+ color="primary"
34
+ size="sm"
35
+ icon="x-mark"
36
+ selected={selected}
37
+ onLabelClick={onToggle}
38
+ onIconClick={onRemove}
39
+ >
40
+ {children}
41
+ </ComboButton>
42
+ );
43
+ }
44
+
45
+ if (type === 'add') {
46
+ return (
47
+ <Button reverse variant="dotted" color="primary" size="sm" icon="plus" selected={selected} onClick={onToggle}>
48
+ {children}
49
+ </Button>
50
+ );
51
+ }
52
+
53
+ return (
54
+ <Button
55
+ variant={active ? 'solid' : 'outline'}
56
+ color="primary"
57
+ size="sm"
58
+ icon="chevron-up-down"
59
+ selected={selected}
60
+ onClick={onToggle}
61
+ >
62
+ {children}
63
+ </Button>
64
+ );
65
+ };
@@ -0,0 +1,66 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { fn } from '@storybook/test';
3
+
4
+ import { ToolbarFilter } from './ToolbarFilter';
5
+
6
+ const meta = {
7
+ title: 'Toolbar/ToolbarFilter',
8
+ component: ToolbarFilter,
9
+ tags: ['autodocs'],
10
+ parameters: {},
11
+ args: {
12
+ label: 'Velg avsender',
13
+ options: [
14
+ {
15
+ label: 'Skatteetaten',
16
+ value: 'skatt',
17
+ },
18
+ {
19
+ label: 'Digdir',
20
+ value: 'digdir',
21
+ },
22
+ {
23
+ label: 'Helstilsynet',
24
+ value: 'helse',
25
+ },
26
+ ],
27
+ },
28
+ } satisfies Meta<typeof ToolbarFilter>;
29
+
30
+ export default meta;
31
+ type Story = StoryObj<typeof meta>;
32
+
33
+ export const Single: Story = {
34
+ args: {
35
+ optionType: 'radio',
36
+ },
37
+ };
38
+
39
+ export const SingleExpanded: Story = {
40
+ args: {
41
+ ...Single.args,
42
+ expanded: true,
43
+ },
44
+ };
45
+
46
+ export const SingleValue: Story = {
47
+ args: {
48
+ optionType: 'radio',
49
+ value: 'helse',
50
+ },
51
+ };
52
+
53
+ export const Multiple: Story = {
54
+ args: {
55
+ optionType: 'checkbox',
56
+ value: ['skatt', 'digdir'],
57
+ },
58
+ };
59
+
60
+ export const MultipleExpanded: Story = {
61
+ args: {
62
+ optionType: 'checkbox',
63
+ value: ['skatt', 'digdir'],
64
+ expanded: true,
65
+ },
66
+ };