@altinn/altinn-components 0.1.0 → 0.2.0

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 (162) hide show
  1. package/.storybook/StoryDecorator.tsx +27 -0
  2. package/.storybook/ThemeProvider.tsx +16 -0
  3. package/.storybook/main.ts +4 -5
  4. package/.storybook/preview.tsx +31 -29
  5. package/.storybook/storyDecorator.module.css +20 -0
  6. package/.storybook/theme.module.css +3 -0
  7. package/CHANGELOG.md +14 -0
  8. package/CONTRIBUTING.MD +59 -0
  9. package/README.md +33 -1
  10. package/lib/components/Attachment/AttachmentLink.stories.ts +21 -0
  11. package/lib/components/Attachment/AttachmentLink.tsx +20 -0
  12. package/lib/components/Attachment/AttachmentList.stories.ts +39 -0
  13. package/lib/components/Attachment/AttachmentList.tsx +26 -0
  14. package/lib/components/Attachment/attachmentLink.module.css +20 -0
  15. package/lib/components/Attachment/attachmentList.module.css +12 -0
  16. package/lib/components/Attachment/index.ts +2 -0
  17. package/lib/components/Avatar/avatar.module.css +2 -0
  18. package/lib/components/Badge/Badge.stories.ts +32 -0
  19. package/lib/components/Badge/Badge.tsx +13 -10
  20. package/lib/components/Badge/badge.module.css +18 -27
  21. package/lib/components/Button/Button.stories.ts +6 -0
  22. package/lib/components/Button/Button.tsx +19 -1
  23. package/lib/components/Button/ButtonBase.tsx +1 -1
  24. package/lib/components/Button/button.module.css +0 -19
  25. package/lib/components/Button/buttonBase.module.css +30 -12
  26. package/lib/components/Button/comboButton.module.css +4 -2
  27. package/lib/components/ContextMenu/ContextMenu.tsx +28 -0
  28. package/lib/components/ContextMenu/contextMenu.module.css +35 -0
  29. package/lib/components/Dialog/Dialog.stories.ts +320 -0
  30. package/lib/components/Dialog/Dialog.tsx +101 -0
  31. package/lib/components/Dialog/DialogAction.stories.ts +54 -0
  32. package/lib/components/Dialog/DialogAction.tsx +79 -0
  33. package/lib/components/Dialog/DialogActivityLog.tsx +18 -0
  34. package/lib/components/Dialog/DialogArticleBase.tsx +10 -0
  35. package/lib/components/Dialog/DialogAttachments.stories.ts +40 -0
  36. package/lib/components/Dialog/DialogAttachments.tsx +25 -0
  37. package/lib/components/Dialog/DialogBase.tsx +10 -0
  38. package/lib/components/Dialog/DialogBodyBase.tsx +17 -0
  39. package/lib/components/Dialog/DialogBorder.tsx +19 -0
  40. package/lib/components/Dialog/DialogContent.stories.ts +26 -0
  41. package/lib/components/Dialog/DialogContent.tsx +24 -0
  42. package/lib/components/Dialog/DialogFooter.tsx +14 -0
  43. package/lib/components/Dialog/DialogHeader.stories.ts +26 -0
  44. package/lib/components/Dialog/DialogHeader.tsx +23 -0
  45. package/lib/components/Dialog/DialogHeaderBase.tsx +10 -0
  46. package/lib/components/Dialog/DialogHeadings.stories.ts +35 -0
  47. package/lib/components/Dialog/DialogHeadings.tsx +77 -0
  48. package/lib/components/Dialog/DialogHistory.stories.ts +67 -0
  49. package/lib/components/Dialog/DialogHistory.tsx +19 -0
  50. package/lib/components/Dialog/DialogList.stories.ts +61 -0
  51. package/lib/components/Dialog/DialogList.tsx +20 -0
  52. package/lib/components/Dialog/DialogListItem.stories.tsx +238 -0
  53. package/lib/components/Dialog/DialogListItem.tsx +114 -0
  54. package/lib/components/Dialog/DialogListItemBase.tsx +50 -0
  55. package/lib/components/Dialog/DialogMetadata.stories.ts +77 -0
  56. package/lib/components/Dialog/DialogMetadata.tsx +56 -0
  57. package/lib/components/Dialog/DialogNav.stories.ts +90 -0
  58. package/lib/components/Dialog/DialogNav.tsx +60 -0
  59. package/lib/components/Dialog/DialogSectionBase.tsx +20 -0
  60. package/lib/components/Dialog/DialogSeenBy.stories.tsx +58 -0
  61. package/lib/components/Dialog/DialogSeenBy.tsx +36 -0
  62. package/lib/components/Dialog/DialogSelect.tsx +23 -0
  63. package/lib/components/Dialog/DialogStatus.stories.ts +57 -0
  64. package/lib/components/Dialog/DialogStatus.tsx +61 -0
  65. package/lib/components/Dialog/DialogTitle.stories.ts +33 -0
  66. package/lib/components/Dialog/DialogTitle.tsx +31 -0
  67. package/lib/components/Dialog/DialogTouchedBy.stories.tsx +27 -0
  68. package/lib/components/Dialog/DialogTouchedBy.tsx +19 -0
  69. package/lib/components/Dialog/dialog.module.css +21 -0
  70. package/lib/components/Dialog/dialogAction.module.css +26 -0
  71. package/lib/components/Dialog/dialogArticleBase.module.css +5 -0
  72. package/lib/components/Dialog/dialogBodyBase.module.css +13 -0
  73. package/lib/components/Dialog/dialogBorder.module.css +42 -0
  74. package/lib/components/Dialog/dialogHeaderBase.module.css +6 -0
  75. package/lib/components/Dialog/dialogHeadings.module.css +24 -0
  76. package/lib/components/Dialog/dialogHistory.module.css +12 -0
  77. package/lib/components/Dialog/dialogListItem.module.css +81 -0
  78. package/lib/components/Dialog/dialogListItemBase.module.css +28 -0
  79. package/lib/components/Dialog/dialogSectionBase.module.css +11 -0
  80. package/lib/components/Dialog/dialogSelect.module.css +34 -0
  81. package/lib/components/Dialog/dialogTitle.module.css +47 -0
  82. package/lib/components/Dialog/index.ts +2 -0
  83. package/lib/components/Header/GlobalMenu.tsx +1 -1
  84. package/lib/components/Header/Header.tsx +3 -3
  85. package/lib/components/Header/HeaderSearch.tsx +1 -1
  86. package/lib/components/History/HistoryBorder.tsx +17 -0
  87. package/lib/components/History/HistoryItem.stories.ts +47 -0
  88. package/lib/components/History/HistoryItem.tsx +64 -0
  89. package/lib/components/History/HistoryList.stories.ts +58 -0
  90. package/lib/components/History/HistoryList.tsx +26 -0
  91. package/lib/components/History/historyBorder.module.css +8 -0
  92. package/lib/components/History/historyItem.module.css +19 -0
  93. package/lib/components/History/historyList.module.css +12 -0
  94. package/lib/components/History/index.ts +2 -0
  95. package/lib/components/Icon/CheckboxCheckedIcon.tsx +28 -0
  96. package/lib/components/Icon/CheckboxIcon.stories.ts +7 -0
  97. package/lib/components/Icon/CheckboxIcon.tsx +6 -18
  98. package/lib/components/Icon/CheckboxUncheckedIcon.tsx +38 -0
  99. package/lib/components/Icon/ProgressIcon.stories.ts +43 -0
  100. package/lib/components/Icon/ProgressIcon.tsx +44 -0
  101. package/lib/components/Icon/RadioCheckedIcon.tsx +29 -0
  102. package/lib/components/Icon/RadioIcon.stories.ts +7 -0
  103. package/lib/components/Icon/RadioIcon.tsx +7 -19
  104. package/lib/components/Icon/RadioUncheckedIcon.tsx +30 -0
  105. package/lib/components/Icon/checkboxIcon.module.css +2 -0
  106. package/lib/components/Icon/index.ts +1 -0
  107. package/lib/components/Icon/progressIcon.module.css +29 -0
  108. package/lib/components/Layout/Layout.stories.ts +0 -3
  109. package/lib/components/List/List.tsx +20 -0
  110. package/lib/components/List/ListBase.tsx +19 -0
  111. package/lib/components/List/ListItem.stories.tsx +208 -0
  112. package/lib/components/List/ListItem.tsx +70 -0
  113. package/lib/components/List/ListItemBase.tsx +62 -0
  114. package/lib/components/List/ListItemLabel.tsx +29 -0
  115. package/lib/components/List/ListItemMedia.tsx +59 -0
  116. package/lib/components/List/index.ts +4 -0
  117. package/lib/components/List/listBase.module.css +16 -0
  118. package/lib/components/List/listItemBase.module.css +86 -0
  119. package/lib/components/List/listItemLabel.module.css +55 -0
  120. package/lib/components/List/listItemMedia.module.css +41 -0
  121. package/lib/components/Menu/Menu.stories.ts +46 -27
  122. package/lib/components/Menu/Menu.tsx +3 -3
  123. package/lib/components/Menu/MenuItem.stories.ts +12 -5
  124. package/lib/components/Menu/MenuItem.tsx +4 -3
  125. package/lib/components/Menu/MenuItemBase.tsx +7 -7
  126. package/lib/components/Menu/MenuItemLabel.tsx +4 -4
  127. package/lib/components/Menu/MenuItemMedia.tsx +2 -2
  128. package/lib/components/Menu/MenuOption.stories.ts +4 -2
  129. package/lib/components/Menu/MenuOption.tsx +4 -4
  130. package/lib/components/Menu/menuItemBase.module.css +72 -0
  131. package/lib/components/Menu/menuItemLabel.module.css +22 -0
  132. package/lib/components/Menu/menuItemMedia.module.css +36 -0
  133. package/lib/components/Menu/menuOption.module.css +6 -8
  134. package/lib/components/Meta/MetaBase.tsx +15 -0
  135. package/lib/components/Meta/MetaItem.stories.ts +25 -0
  136. package/lib/components/Meta/MetaItem.tsx +31 -0
  137. package/lib/components/Meta/MetaItemBase.tsx +46 -0
  138. package/lib/components/Meta/MetaItemLabel.tsx +20 -0
  139. package/lib/components/Meta/MetaItemMedia.tsx +22 -0
  140. package/lib/components/Meta/MetaList.stories.ts +29 -0
  141. package/lib/components/Meta/MetaList.tsx +43 -0
  142. package/lib/components/Meta/MetaProgress.stories.ts +29 -0
  143. package/lib/components/Meta/MetaProgress.tsx +26 -0
  144. package/lib/components/Meta/MetaTimestamp.stories.ts +33 -0
  145. package/lib/components/Meta/MetaTimestamp.tsx +29 -0
  146. package/lib/components/Meta/index.ts +6 -0
  147. package/lib/components/Meta/meta.module.css +6 -0
  148. package/lib/components/Meta/metaItem.module.css +107 -0
  149. package/lib/components/Meta/metaList.module.css +15 -0
  150. package/lib/components/Toolbar/ToolbarAdd.tsx +1 -1
  151. package/lib/components/Typography/Typography.tsx +21 -0
  152. package/lib/components/Typography/index.ts +1 -0
  153. package/lib/components/Typography/typography.module.css +56 -0
  154. package/lib/components/index.ts +9 -0
  155. package/lib/css/global.css +2 -0
  156. package/lib/css/shadows.css +7 -0
  157. package/lib/css/theme-article.css +15 -0
  158. package/lib/css/theme.css +5 -7
  159. package/package.json +4 -2
  160. package/renovate.json +1 -3
  161. package/.storybook/preview.css +0 -18
  162. /package/lib/components/Toolbar/{index.js → index.ts} +0 -0
@@ -0,0 +1,238 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Fragment, useState } from 'react';
3
+
4
+ import { DialogListItem } from './DialogListItem';
5
+ import { DialogStatusEnum } from './DialogStatus';
6
+
7
+ import { ListBase } from '../List';
8
+ import { MetaItem } from '../Meta';
9
+
10
+ const getStatusLabel = (value) => {
11
+ switch (value) {
12
+ case 'draft':
13
+ return 'Utkast';
14
+ case 'sent':
15
+ return 'Sendt';
16
+ case 'requires-attention':
17
+ return 'Krever handling';
18
+ case 'in-progress':
19
+ return 'Under arbeid';
20
+ case 'completed':
21
+ return 'Avsluttet';
22
+ default:
23
+ return '';
24
+ }
25
+ };
26
+
27
+ const sizes = ['lg', 'md', 'sm', 'xs'];
28
+ const statuslist = Object.keys(DialogStatusEnum)?.map((value) => {
29
+ return {
30
+ value,
31
+ label: getStatusLabel(value),
32
+ };
33
+ });
34
+
35
+ const meta = {
36
+ title: 'Dialog/DialogListItem',
37
+ component: DialogListItem,
38
+ tags: ['autodocs'],
39
+ parameters: {},
40
+ argTypes: {},
41
+ args: {
42
+ title: 'Title',
43
+ summary: 'Summary',
44
+ sender: {
45
+ type: 'company',
46
+ name: 'Sender name',
47
+ },
48
+ recipient: {
49
+ type: 'person',
50
+ name: 'Recipient name',
51
+ },
52
+ status: {
53
+ value: 'completed',
54
+ },
55
+ },
56
+ } satisfies Meta<typeof DialogListItem>;
57
+
58
+ export default meta;
59
+ type Story = StoryObj<typeof meta>;
60
+
61
+ export const selectable: Story = {
62
+ args: {
63
+ select: {
64
+ checked: false,
65
+ },
66
+ },
67
+ };
68
+
69
+ export const selected: Story = {
70
+ args: {
71
+ selected: true,
72
+ select: {
73
+ checked: true,
74
+ },
75
+ },
76
+ };
77
+
78
+ export const seenByEndUser: Story = {
79
+ args: {
80
+ seenByEndUser: true,
81
+ },
82
+ };
83
+
84
+ export const TouchedBy: Story = {
85
+ args: {
86
+ touchedBy: [
87
+ {
88
+ name: 'Lars',
89
+ },
90
+ {
91
+ name: 'Trine',
92
+ },
93
+ ],
94
+ },
95
+ };
96
+
97
+ export const Draft: Story = {
98
+ args: {
99
+ status: {
100
+ value: 'draft',
101
+ },
102
+ },
103
+ };
104
+
105
+ export const Sent: Story = {
106
+ args: {
107
+ status: {
108
+ value: 'sent',
109
+ },
110
+ },
111
+ };
112
+
113
+ export const RequiresAttention: Story = {
114
+ args: {
115
+ status: {
116
+ value: 'requires-attention',
117
+ },
118
+ },
119
+ };
120
+
121
+ export const InProgress: Story = {
122
+ args: {
123
+ status: {
124
+ value: 'in-progress',
125
+ },
126
+ },
127
+ };
128
+
129
+ export const Completed: Story = {
130
+ args: {
131
+ status: {
132
+ value: 'completed',
133
+ },
134
+ },
135
+ };
136
+
137
+ export const GroupedView: Story = {
138
+ args: {
139
+ grouped: true,
140
+ },
141
+ };
142
+
143
+ export const LongSummary: Story = {
144
+ args: {
145
+ title: 'Long summary',
146
+ summary:
147
+ 'Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Etiam porta sem malesuada magna mollis euismod. Maecenas faucibus mollis interdum. Nullam id dolor id nibh ultricies vehicula ut id elit.\n\nCras mattis consectetur purus sit amet fermentum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.',
148
+ },
149
+ };
150
+
151
+ export const LongTitle: Story = {
152
+ args: {
153
+ title:
154
+ 'Cras mattis consectetur purus sit amet fermentum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.',
155
+ summary: 'Short summary.',
156
+ },
157
+ };
158
+
159
+ export const SelectableSelected = (args) => {
160
+ const [items, setItems] = useState({
161
+ 1: {
162
+ id: '1',
163
+ title: 'Item 1',
164
+ selected: true,
165
+ },
166
+ 2: {
167
+ id: '2',
168
+ title: 'Item 2',
169
+ selected: false,
170
+ },
171
+ 3: {
172
+ id: '3',
173
+ title: 'Item 2',
174
+ selected: false,
175
+ },
176
+ });
177
+
178
+ const onSelect = ({ id }) => {
179
+ setItems((prevState) => {
180
+ return {
181
+ ...prevState,
182
+ [id]: {
183
+ ...prevState[id],
184
+ selected: !prevState[id].selected,
185
+ },
186
+ };
187
+ });
188
+ };
189
+
190
+ return (
191
+ <ListBase>
192
+ {Object.values(items)?.map((item) => {
193
+ return (
194
+ <Fragment key={item?.id}>
195
+ <DialogListItem
196
+ {...args}
197
+ title={item.title}
198
+ onClick={item.selected ? () => onSelect(item) : null}
199
+ selected={item.selected}
200
+ select={{ checked: item?.selected, onChange: () => onSelect(item) }}
201
+ />
202
+ <MetaItem>selected:{item.selected ? 'true' : 'false'}</MetaItem>
203
+ </Fragment>
204
+ );
205
+ })}
206
+ </ListBase>
207
+ );
208
+ };
209
+
210
+ export const Statuses = (args) => {
211
+ return (
212
+ <ListBase>
213
+ {statuslist?.map((status) => {
214
+ return (
215
+ <Fragment key={status?.value}>
216
+ <DialogListItem {...args} status={status} />
217
+ <MetaItem>{status?.value}</MetaItem>
218
+ </Fragment>
219
+ );
220
+ })}
221
+ </ListBase>
222
+ );
223
+ };
224
+
225
+ export const Sizes = (args) => {
226
+ return (
227
+ <ListBase>
228
+ {sizes?.map((size) => {
229
+ return (
230
+ <Fragment key={size}>
231
+ <DialogListItem {...args} size={size} />
232
+ <MetaItem>{size}</MetaItem>
233
+ </Fragment>
234
+ );
235
+ })}
236
+ </ListBase>
237
+ );
238
+ };
@@ -0,0 +1,114 @@
1
+ import type { ElementType } from 'react';
2
+ import { DialogHeadings, type DialogRecipientProps, type DialogSenderProps } from './DialogHeadings';
3
+ import type { DialogSelectProps } from './DialogSelect';
4
+ import type { DialogStatusProps } from './DialogStatus';
5
+ import { DialogTitle } from './DialogTitle';
6
+ import { DialogTouchedBy, type DialogTouchedByActor } from './DialogTouchedBy';
7
+ import styles from './dialogListItem.module.css';
8
+
9
+ import { DialogListItemBase, type DialogListItemSize, type DialogListItemVariant } from './DialogListItemBase';
10
+
11
+ import { DialogBorder } from './DialogBorder';
12
+ import { DialogMetadata } from './DialogMetadata';
13
+ import type { DialogSeenByProps } from './DialogSeenBy';
14
+
15
+ export type DialogListItemProps = {
16
+ /** Dialog title */
17
+ title: string;
18
+ /** Render as */
19
+ as?: ElementType;
20
+ /** Size */
21
+ size?: DialogListItemSize;
22
+ /** Variant */
23
+ variant?: DialogListItemVariant;
24
+ /** Link */
25
+ href?: string;
26
+ /** Select: Use to support batch operations */
27
+ select?: DialogSelectProps;
28
+ /** Dialog is selected */
29
+ selected?: boolean;
30
+ /** Dialog status */
31
+ status?: DialogStatusProps;
32
+ /** Dialog sender */
33
+ sender?: DialogSenderProps;
34
+ /** Dialog Recipient */
35
+ recipient?: DialogRecipientProps;
36
+ /** Group view, show avatar for recipient */
37
+ grouped?: boolean;
38
+ /** Dialog summary */
39
+ summary?: string;
40
+ /** Updated datetime */
41
+ updatedAt?: string;
42
+ /** Updated at label */
43
+ updatedAtLabel?: string;
44
+ /** Dialog due date */
45
+ dueAt?: string;
46
+ /** Dialog due date label */
47
+ dueAtLabel?: string;
48
+ /** Dialog has been seen */
49
+ seen?: boolean;
50
+ /** Dialog is seen by the user */
51
+ seenBy?: DialogSeenByProps;
52
+ /** List of users that have touched the dialog */
53
+ touchedBy?: DialogTouchedByActor[];
54
+ /** Number of attachments */
55
+ attachmentsCount?: number;
56
+ };
57
+
58
+ /**
59
+ * Represents a dialog in list view, displaying information such as the title,
60
+ * summary, sender, and receiver.
61
+ * to mark the item as checked/unchecked and can visually indicate if it is unread.
62
+ */
63
+
64
+ export const DialogListItem = ({
65
+ as = 'a',
66
+ size = 'lg',
67
+ variant = 'neutral',
68
+ href,
69
+ select,
70
+ selected,
71
+ status,
72
+ sender,
73
+ recipient,
74
+ grouped,
75
+ updatedAt,
76
+ updatedAtLabel,
77
+ dueAt,
78
+ dueAtLabel,
79
+ seen = false,
80
+ seenBy,
81
+ touchedBy,
82
+ attachmentsCount,
83
+ title,
84
+ summary,
85
+ ...rest
86
+ }: DialogListItemProps) => {
87
+ return (
88
+ <DialogListItemBase as={as} size={size} href={href} select={select} selected={selected} variant={variant} {...rest}>
89
+ <DialogBorder className={styles.border} size={size} seen={seen}>
90
+ <header data-size={size} className={styles.header}>
91
+ <DialogTitle size={size} seen={seen} variant={variant}>
92
+ {title}
93
+ </DialogTitle>
94
+ <DialogHeadings size="xs" grouped={grouped} sender={sender} recipient={recipient} />
95
+ </header>
96
+ <p data-size={size} className={styles.summary}>
97
+ {summary}
98
+ </p>
99
+ <footer data-size={size} className={styles.footer}>
100
+ <DialogMetadata
101
+ status={status}
102
+ updatedAt={updatedAt}
103
+ updatedAtLabel={updatedAtLabel}
104
+ dueAt={dueAt}
105
+ dueAtLabel={dueAtLabel}
106
+ seenBy={seenBy}
107
+ attachmentsCount={attachmentsCount}
108
+ />
109
+ {touchedBy && <DialogTouchedBy size="xs" touchedBy={touchedBy} className={styles.touchedBy} />}
110
+ </footer>
111
+ </DialogBorder>
112
+ </DialogListItemBase>
113
+ );
114
+ };
@@ -0,0 +1,50 @@
1
+ import type { ElementType, ReactNode } from 'react';
2
+ import { DialogSelect, type DialogSelectProps } from './DialogSelect';
3
+ import styles from './dialogListItemBase.module.css';
4
+
5
+ export type DialogListItemVariant = 'neutral' | 'draft' | 'bin' | 'archive';
6
+ export type DialogListItemSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
7
+
8
+ export type DialogListItemBaseProps = {
9
+ /** Render as */
10
+ as?: ElementType;
11
+ /** Size */
12
+ size?: DialogListItemSize;
13
+ /** Link */
14
+ href?: string;
15
+ /** Select? Use to support batch operations */
16
+ select?: DialogSelectProps;
17
+ /** Dialog is selected */
18
+ selected?: boolean;
19
+ /** Children */
20
+ children?: ReactNode;
21
+ /** Variant */
22
+ variant?: DialogListItemVariant;
23
+ };
24
+
25
+ /**
26
+ * Represents a dialog in list view, displaying information such as the title,
27
+ * summary, sender, and receiver.
28
+ * to mark the item as checked/unchecked and can visually indicate if it is unread.
29
+ */
30
+
31
+ export const DialogListItemBase = ({
32
+ as = 'a',
33
+ size,
34
+ href,
35
+ select,
36
+ selected,
37
+ children,
38
+ ...rest
39
+ }: DialogListItemBaseProps) => {
40
+ const Component = as || 'button';
41
+
42
+ return (
43
+ <article className={styles.item} data-size={size} aria-selected={selected}>
44
+ <Component className={styles.link} data-size={size} href={href} {...rest}>
45
+ {children}
46
+ </Component>
47
+ {select && <DialogSelect className={styles.select} {...select} />}
48
+ </article>
49
+ );
50
+ };
@@ -0,0 +1,77 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { fn } from '@storybook/test';
3
+
4
+ import { DialogMetadata } from './DialogMetadata';
5
+
6
+ const meta = {
7
+ title: 'Dialog/DialogMetadata',
8
+ component: DialogMetadata,
9
+ tags: ['autodocs'],
10
+ parameters: {},
11
+ args: {
12
+ updatedAt: '1999-05-26',
13
+ updatedAtLabel: '26. mai 1999',
14
+ },
15
+ } satisfies Meta<typeof DialogMetadata>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Draft: Story = {
21
+ args: {
22
+ status: {
23
+ value: 'draft',
24
+ label: 'Utkast',
25
+ },
26
+ updatedAtLabel: 'Ole Gunnar Solskjær, 26. mai 1999',
27
+ },
28
+ };
29
+
30
+ export const Sent: Story = {
31
+ args: {
32
+ status: {
33
+ value: 'sent',
34
+ label: 'Sendt',
35
+ },
36
+ },
37
+ };
38
+
39
+ export const RequiresAttentionAndDueDate: Story = {
40
+ args: {
41
+ status: {
42
+ value: 'requires-attention',
43
+ label: 'Krever handling',
44
+ },
45
+ attachmentsCount: 3,
46
+ dueAt: '2000-01-01',
47
+ dueAtLabel: 'Frist: 1. januar 2001',
48
+ },
49
+ };
50
+
51
+ export const InProgressSeenByOthers: Story = {
52
+ args: {
53
+ status: {
54
+ value: 'in-progress',
55
+ label: 'Under arbeid',
56
+ },
57
+ seenBy: {
58
+ seenByEndUser: false,
59
+ seenByOthersCount: 4,
60
+ label: 'Sett av 4',
61
+ },
62
+ },
63
+ };
64
+
65
+ export const CompletedSeenByEndUser: Story = {
66
+ args: {
67
+ status: {
68
+ value: 'completed',
69
+ label: 'Avsluttet',
70
+ },
71
+ seenBy: {
72
+ seenByEndUser: true,
73
+ seenByOthersCount: 0,
74
+ label: 'Sett av deg',
75
+ },
76
+ },
77
+ };
@@ -0,0 +1,56 @@
1
+ import { MetaBase, MetaItem, MetaTimestamp } from '../Meta';
2
+ import { DialogSeenBy, type DialogSeenByProps } from './DialogSeenBy';
3
+ import { DialogStatus, type DialogStatusProps } from './DialogStatus';
4
+
5
+ export type DialogMetadataProps = {
6
+ /** Dialog status */
7
+ status?: DialogStatusProps;
8
+ /** Updated datetime */
9
+ updatedAt?: string;
10
+ /** Updated label */
11
+ updatedAtLabel?: string;
12
+ /** Due date */
13
+ dueAt?: string;
14
+ /** Due date label */
15
+ dueAtLabel?: string;
16
+ /** Who have seen the dialog after latest update */
17
+ seenBy?: DialogSeenByProps;
18
+ /** Number of attachments */
19
+ attachmentsCount?: number;
20
+ };
21
+
22
+ /**
23
+ * Metadata for a dialog in list view.
24
+ */
25
+
26
+ export const DialogMetadata = ({
27
+ status,
28
+ updatedAt,
29
+ updatedAtLabel,
30
+ dueAt,
31
+ dueAtLabel,
32
+ seenBy,
33
+ attachmentsCount = 0,
34
+ }: DialogMetadataProps) => {
35
+ return (
36
+ <MetaBase size="xs">
37
+ {status && <DialogStatus size="xs" {...status} />}
38
+ {updatedAt && (
39
+ <MetaTimestamp datetime={updatedAt} size="xs">
40
+ {updatedAtLabel}
41
+ </MetaTimestamp>
42
+ )}
43
+ {dueAt && dueAtLabel && (
44
+ <MetaTimestamp datetime={dueAt} size="xs" icon="clock-dashed">
45
+ {dueAtLabel}
46
+ </MetaTimestamp>
47
+ )}
48
+ {seenBy && <DialogSeenBy size="xs" {...seenBy} />}
49
+ {attachmentsCount > 0 && (
50
+ <MetaItem size="xs" icon="paperclip">
51
+ {attachmentsCount}
52
+ </MetaItem>
53
+ )}
54
+ </MetaBase>
55
+ );
56
+ };
@@ -0,0 +1,90 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { fn } from '@storybook/test';
3
+
4
+ import { DialogNav } from './DialogNav';
5
+
6
+ const meta = {
7
+ title: 'Dialog/DialogNav',
8
+ component: DialogNav,
9
+ tags: ['autodocs'],
10
+ parameters: {},
11
+ args: {},
12
+ } satisfies Meta<typeof DialogNav>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Draft: Story = {
18
+ args: {
19
+ status: {
20
+ value: 'draft',
21
+ label: 'Utkast',
22
+ },
23
+ },
24
+ };
25
+
26
+ export const Sent: Story = {
27
+ args: {
28
+ status: {
29
+ value: 'draft',
30
+ label: 'Utkast',
31
+ },
32
+ },
33
+ };
34
+
35
+ export const RequiresAttention: Story = {
36
+ args: {
37
+ status: {
38
+ value: 'requires-attention',
39
+ label: 'Utkast',
40
+ },
41
+ },
42
+ };
43
+
44
+ export const InProgress: Story = {
45
+ args: {
46
+ status: {
47
+ value: 'in-progress',
48
+ label: 'Utkast',
49
+ },
50
+ },
51
+ };
52
+
53
+ export const ContextMenu: Story = {
54
+ args: {
55
+ menu: {
56
+ items: [
57
+ {
58
+ id: '1',
59
+ group: '1',
60
+ icon: 'arrow-redo',
61
+ label: 'Del og gi tilgang',
62
+ },
63
+ {
64
+ id: '2',
65
+ group: '1',
66
+ icon: 'eye-closed',
67
+ label: 'Marker som ny',
68
+ },
69
+ {
70
+ id: '3',
71
+ group: '2',
72
+ icon: 'archive',
73
+ label: 'Flytt til arkiv',
74
+ },
75
+ {
76
+ id: '4',
77
+ group: '2',
78
+ icon: 'trash',
79
+ label: 'Flytt til papirkurv',
80
+ },
81
+ {
82
+ id: '5',
83
+ group: '3',
84
+ icon: 'clock-dashed',
85
+ label: 'Aktivitetslogg',
86
+ },
87
+ ],
88
+ },
89
+ },
90
+ };
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+ import { useState } from 'react';
3
+ import type { ElementType } from 'react';
4
+ import { Button } from '../Button';
5
+ import { ContextMenu, type DialogContextMenuProps } from '../ContextMenu/ContextMenu.tsx';
6
+ import { MetaTimestamp } from '../Meta';
7
+ import { DialogStatus, type DialogStatusProps } from './DialogStatus';
8
+ import { DialogTouchedBy, type DialogTouchedByActor } from './DialogTouchedBy';
9
+ import styles from './dialog.module.css';
10
+
11
+ export interface DialogBackButtonProps {
12
+ as?: ElementType;
13
+ href?: string;
14
+ label?: string;
15
+ }
16
+
17
+ export interface DialogNavProps {
18
+ status?: DialogStatusProps;
19
+ dueAt?: string;
20
+ duaAtLabel?: string;
21
+ touchedBy?: DialogTouchedByActor[];
22
+ backButton?: DialogBackButtonProps;
23
+ menu?: DialogContextMenuProps;
24
+ }
25
+
26
+ /**
27
+ * Dialog navigation bar with Back button and possibly a context menu.
28
+ */
29
+ export const DialogNav = ({
30
+ backButton = {
31
+ as: 'a',
32
+ label: 'Back',
33
+ },
34
+ status,
35
+ dueAt,
36
+ touchedBy,
37
+ duaAtLabel,
38
+ menu,
39
+ }: DialogNavProps) => {
40
+ const [expandedItem, setexpandedItem] = useState<boolean>(false);
41
+ const onToggle = () => setexpandedItem((expanded) => !expanded);
42
+
43
+ return (
44
+ <nav className={styles.nav}>
45
+ <Button {...backButton} variant="text" color="secondary" icon="arrow-left" reverse>
46
+ {backButton?.label || 'Back'}
47
+ </Button>
48
+ <div className={styles.action}>
49
+ {duaAtLabel && duaAtLabel && (
50
+ <MetaTimestamp datetime={dueAt} size="xs">
51
+ {duaAtLabel}
52
+ </MetaTimestamp>
53
+ )}
54
+ {status && <DialogStatus {...status} />}
55
+ {touchedBy && <DialogTouchedBy touchedBy={touchedBy} />}
56
+ {menu && <ContextMenu {...menu} expanded={expandedItem} onToggle={onToggle} />}
57
+ </div>
58
+ </nav>
59
+ );
60
+ };
@@ -0,0 +1,20 @@
1
+ import type { ReactNode } from 'react';
2
+ import styles from './dialogSectionBase.module.css';
3
+
4
+ export interface DialogSectionBaseProps {
5
+ title?: string;
6
+ children?: ReactNode;
7
+ }
8
+
9
+ export const DialogSectionBase = ({ title, children }: DialogSectionBaseProps) => {
10
+ if (!children) {
11
+ return null;
12
+ }
13
+
14
+ return (
15
+ <section className={styles.section}>
16
+ {title && <h2 className={styles.title}>{title}</h2>}
17
+ {children}
18
+ </section>
19
+ );
20
+ };