@drivenets/design-system 0.6.1 → 0.7.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 (134) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/index.cjs +12394 -8354
  3. package/dist/index.d.cts +806 -118
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +806 -118
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +12376 -8357
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +25 -23
  11. package/src/components/ds-autocomplete/ds-autocomplete.module.scss +2 -1
  12. package/src/components/ds-autocomplete/ds-autocomplete.tsx +47 -40
  13. package/src/components/ds-autocomplete/ds-autocomplete.types.tsx +37 -14
  14. package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +3 -7
  15. package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.module.scss +62 -0
  16. package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.tsx +83 -0
  17. package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.types.ts +12 -0
  18. package/src/components/ds-comment-bubble/components/ds-comment-thread/index.ts +2 -0
  19. package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.module.scss +149 -0
  20. package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.tsx +147 -0
  21. package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.types.ts +15 -0
  22. package/src/components/ds-comment-bubble/components/ds-thread-item/index.ts +2 -0
  23. package/src/components/ds-comment-bubble/ds-comment-bubble.module.scss +298 -0
  24. package/src/components/ds-comment-bubble/ds-comment-bubble.tsx +241 -0
  25. package/src/components/ds-comment-bubble/ds-comment-bubble.types.ts +27 -0
  26. package/src/components/ds-comment-bubble/hooks/index.ts +3 -0
  27. package/src/components/ds-comment-bubble/hooks/use-auto-resize.ts +17 -0
  28. package/src/components/ds-comment-bubble/hooks/use-click-outside.ts +22 -0
  29. package/src/components/ds-comment-bubble/hooks/use-scroll-overflow.ts +27 -0
  30. package/src/components/ds-comment-bubble/index.ts +6 -0
  31. package/src/components/ds-comment-card/ds-comment-card.module.scss +162 -0
  32. package/src/components/ds-comment-card/ds-comment-card.tsx +171 -0
  33. package/src/components/ds-comment-card/ds-comment-card.types.ts +45 -0
  34. package/src/components/ds-comment-card/index.ts +2 -0
  35. package/src/components/ds-comment-indicator/ds-comment-indicator.module.scss +65 -0
  36. package/src/components/ds-comment-indicator/ds-comment-indicator.tsx +48 -0
  37. package/src/components/ds-comment-indicator/ds-comment-indicator.types.ts +13 -0
  38. package/src/components/ds-comment-indicator/index.ts +2 -0
  39. package/src/components/ds-comments-drawer/comments-filters.test.ts +483 -0
  40. package/src/components/ds-comments-drawer/comments-filters.ts +134 -0
  41. package/src/components/ds-comments-drawer/comments-filters.types.ts +14 -0
  42. package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.module.scss +92 -0
  43. package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx +219 -0
  44. package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.types.ts +12 -0
  45. package/src/components/ds-comments-drawer/components/comments-filter-modal/index.ts +2 -0
  46. package/src/components/ds-comments-drawer/ds-comments-drawer.module.scss +67 -0
  47. package/src/components/ds-comments-drawer/ds-comments-drawer.tsx +215 -0
  48. package/src/components/ds-comments-drawer/ds-comments-drawer.types.ts +23 -0
  49. package/src/components/ds-comments-drawer/index.ts +3 -0
  50. package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +2 -0
  51. package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +8 -4
  52. package/src/components/ds-file-upload/types/accept-types.ts +0 -1
  53. package/src/components/ds-filter-status-icon/ds-filter-status-icon.module.scss +19 -0
  54. package/src/components/ds-filter-status-icon/ds-filter-status-icon.tsx +35 -0
  55. package/src/components/ds-filter-status-icon/ds-filter-status-icon.types.ts +31 -0
  56. package/src/components/ds-filter-status-icon/index.ts +3 -0
  57. package/src/components/ds-icon/custom-icons/index.ts +10 -0
  58. package/src/components/ds-icon/custom-icons/special-failed.tsx +16 -0
  59. package/src/components/ds-icon/custom-icons/special-paused.tsx +16 -0
  60. package/src/components/ds-icon/custom-icons/special-running.tsx +16 -0
  61. package/src/components/ds-icon/custom-icons/special-warning.tsx +16 -0
  62. package/src/components/ds-panel/ds-panel.module.scss +3 -3
  63. package/src/components/ds-progress-arc/ds-progress-arc.module.scss +57 -0
  64. package/src/components/ds-progress-arc/ds-progress-arc.tsx +111 -0
  65. package/src/components/ds-progress-arc/ds-progress-arc.types.ts +34 -0
  66. package/src/components/ds-progress-arc/index.ts +3 -0
  67. package/src/components/ds-progress-arc/utils.test.ts +79 -0
  68. package/src/components/ds-progress-arc/utils.ts +61 -0
  69. package/src/components/ds-progress-donut/ds-progress-donut.module.scss +67 -0
  70. package/src/components/ds-progress-donut/ds-progress-donut.tsx +83 -0
  71. package/src/components/ds-progress-donut/ds-progress-donut.types.ts +38 -0
  72. package/src/components/ds-progress-donut/index.ts +2 -0
  73. package/src/components/ds-progress-donut/utils.ts +1 -0
  74. package/src/components/ds-progress-linear/ds-progress-linear.module.scss +100 -0
  75. package/src/components/ds-progress-linear/ds-progress-linear.tsx +107 -0
  76. package/src/components/ds-progress-linear/ds-progress-linear.types.ts +72 -0
  77. package/src/components/ds-progress-linear/index.ts +2 -0
  78. package/src/components/ds-progress-linear/utils.test.ts +70 -0
  79. package/src/components/ds-progress-linear/utils.ts +7 -0
  80. package/src/components/ds-radio-group/ds-radio-group.tsx +2 -0
  81. package/src/components/ds-select/ds-select.tsx +2 -1
  82. package/src/components/ds-select/ds-select.types.tsx +7 -0
  83. package/src/components/ds-stepper/components/ds-step.tsx +1 -1
  84. package/src/components/ds-stepper/ds-stepper.module.scss +16 -2
  85. package/src/components/ds-stepper/ds-stepper.tsx +3 -1
  86. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +9 -0
  87. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +137 -0
  88. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +22 -0
  89. package/src/components/ds-table/components/ds-table-body-virtualized/index.ts +2 -0
  90. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +3 -1
  91. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +7 -10
  92. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +3 -4
  93. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +13 -53
  94. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +32 -85
  95. package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +0 -6
  96. package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.module.scss +10 -0
  97. package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.tsx +45 -0
  98. package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.types.ts +7 -0
  99. package/src/components/ds-table/components/ds-table-row-expandable-cell/index.ts +2 -0
  100. package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.module.scss +6 -0
  101. package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.tsx +30 -0
  102. package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.types.ts +6 -0
  103. package/src/components/ds-table/components/ds-table-row-selectable-cell/index.ts +2 -0
  104. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +80 -0
  105. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +95 -0
  106. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +11 -0
  107. package/src/components/ds-table/components/ds-table-row-virtualized/index.ts +2 -0
  108. package/src/components/ds-table/context/ds-table-context.tsx +0 -9
  109. package/src/components/ds-table/ds-table.module.scss +4 -17
  110. package/src/components/ds-table/ds-table.tsx +44 -77
  111. package/src/components/ds-table/ds-table.types.ts +57 -12
  112. package/src/components/ds-table/index.ts +1 -0
  113. package/src/components/ds-table/styles/_mixins.scss +15 -0
  114. package/src/components/ds-table/styles/_variables.scss +2 -2
  115. package/src/components/ds-table/utils/{column-styling.ts → column-size.ts} +4 -14
  116. package/src/components/ds-table/utils/constants.ts +4 -0
  117. package/src/components/ds-tag/index.tsx +1 -0
  118. package/src/components/ds-tag-filter/ds-tag-filter.module.scss +13 -15
  119. package/src/components/ds-tag-filter/ds-tag-filter.tsx +38 -72
  120. package/src/components/ds-tag-filter/ds-tag-filter.types.ts +2 -3
  121. package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +12 -62
  122. package/src/components/ds-tag-filter/utils.test.ts +2 -108
  123. package/src/components/ds-tag-filter/utils.ts +2 -12
  124. package/src/components/ds-tree/ds-tree.module.scss +283 -0
  125. package/src/components/ds-tree/ds-tree.tsx +172 -0
  126. package/src/components/ds-tree/ds-tree.types.ts +83 -0
  127. package/src/components/ds-tree/ds-tree.utils.ts +11 -0
  128. package/src/components/ds-tree/index.ts +3 -0
  129. package/src/index.ts +9 -0
  130. package/src/utils/format-relative-time.test.ts +74 -0
  131. package/src/utils/format-relative-time.ts +52 -0
  132. package/src/utils/numbers.test.ts +24 -0
  133. package/src/utils/numbers.ts +1 -0
  134. package/src/components/ds-table/utils/create-columns-grid-template.ts +0 -23
@@ -0,0 +1,27 @@
1
+ import type { CSSProperties, ReactNode, Ref } from 'react';
2
+ import type { CommentAuthor, CommentData } from '../ds-comment-card';
3
+
4
+ export interface DsCommentBubbleProps {
5
+ ref?: Ref<HTMLDivElement>;
6
+ comment?: CommentData;
7
+ currentUser?: CommentAuthor;
8
+ actionRequired?: boolean;
9
+ onActionRequiredChange?: (checked: boolean) => void;
10
+ value?: string;
11
+ onValueChange?: (value: string) => void;
12
+ onSend?: (content: string, actionRequired: boolean) => void;
13
+ onClose?: () => void;
14
+ onResolve?: () => void;
15
+ onToggleActionRequired?: () => void;
16
+ onForward?: () => void;
17
+ onMarkUnread?: () => void;
18
+ onCopyLink?: () => void;
19
+ onDelete?: () => void;
20
+ onEditMessage?: (messageId: string, newContent: string) => void;
21
+ onDeleteMessage?: (messageId: string) => void;
22
+ onMessageMarkUnread?: (messageId: string) => void;
23
+ onMessageResolved?: (messageId: string) => void;
24
+ referenceTag?: ReactNode;
25
+ className?: string;
26
+ style?: CSSProperties;
27
+ }
@@ -0,0 +1,3 @@
1
+ export { useClickOutside } from './use-click-outside';
2
+ export { useAutoResize } from './use-auto-resize';
3
+ export { useScrollOverflow } from './use-scroll-overflow';
@@ -0,0 +1,17 @@
1
+ import { useLayoutEffect, type RefObject } from 'react';
2
+
3
+ export const useAutoResize = (
4
+ ref: RefObject<HTMLTextAreaElement | null>,
5
+ value: string,
6
+ maxHeight: number = 480,
7
+ ): void => {
8
+ useLayoutEffect(() => {
9
+ const textarea = ref.current;
10
+ if (!textarea) {
11
+ return;
12
+ }
13
+
14
+ textarea.style.height = 'auto';
15
+ textarea.style.height = `${String(Math.min(textarea.scrollHeight, maxHeight))}px`;
16
+ }, [ref, value, maxHeight]);
17
+ };
@@ -0,0 +1,22 @@
1
+ import { useEffect, type RefObject } from 'react';
2
+
3
+ export const useClickOutside = (
4
+ ref: RefObject<HTMLElement | null>,
5
+ onClickOutside: (() => void) | undefined,
6
+ enabled: boolean = true,
7
+ ): void => {
8
+ useEffect(() => {
9
+ if (!enabled || !onClickOutside) {
10
+ return;
11
+ }
12
+
13
+ const handleClickOutside = (event: MouseEvent) => {
14
+ if (ref.current && !ref.current.contains(event.target as Node)) {
15
+ onClickOutside();
16
+ }
17
+ };
18
+
19
+ document.addEventListener('mousedown', handleClickOutside);
20
+ return () => document.removeEventListener('mousedown', handleClickOutside);
21
+ }, [ref, onClickOutside, enabled]);
22
+ };
@@ -0,0 +1,27 @@
1
+ import { useState, useLayoutEffect, type RefObject } from 'react';
2
+
3
+ export const useScrollOverflow = (ref: RefObject<HTMLElement | null>, dependency: number): boolean => {
4
+ const [hasOverflow, setHasOverflow] = useState(false);
5
+
6
+ useLayoutEffect(() => {
7
+ const container = ref.current;
8
+ if (!container) {
9
+ return;
10
+ }
11
+
12
+ const checkOverflow = () => {
13
+ const isOverflowing = container.scrollHeight > container.clientHeight;
14
+ const isScrolled = container.scrollTop > 0;
15
+ setHasOverflow(isOverflowing && isScrolled);
16
+ };
17
+
18
+ checkOverflow();
19
+ container.addEventListener('scroll', checkOverflow, { passive: true });
20
+
21
+ return () => {
22
+ container.removeEventListener('scroll', checkOverflow);
23
+ };
24
+ }, [ref, dependency]);
25
+
26
+ return hasOverflow;
27
+ };
@@ -0,0 +1,6 @@
1
+ export { DsCommentBubble } from './ds-comment-bubble';
2
+ export * from './ds-comment-bubble.types';
3
+ export { DsCommentThread } from './components/ds-comment-thread';
4
+ export * from './components/ds-comment-thread/ds-comment-thread.types';
5
+ export { DsThreadItem } from './components/ds-thread-item';
6
+ export * from './components/ds-thread-item/ds-thread-item.types';
@@ -0,0 +1,162 @@
1
+ @import '../../styles/mixin';
2
+
3
+ $side-color-width: 4px;
4
+ $border-width: 1px;
5
+ $line-clamp-count: 3;
6
+ $transition-duration: 0.2s;
7
+
8
+ .card {
9
+ display: flex;
10
+ width: 100%;
11
+ border: $border-width solid var(--color-border);
12
+ border-radius: var(--spacing-3xs);
13
+ background: var(--color-background);
14
+ overflow: hidden;
15
+ cursor: pointer;
16
+ text-align: left;
17
+ flex-shrink: 0;
18
+ transition:
19
+ border-color $transition-duration ease,
20
+ box-shadow $transition-duration ease;
21
+
22
+ &:hover {
23
+ background: var(--color-background-action-hover-weak);
24
+ border-color: var(--color-border);
25
+ box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
26
+
27
+ .actions {
28
+ opacity: 1;
29
+ visibility: visible;
30
+ }
31
+ }
32
+
33
+ @include disabled-overlay;
34
+ }
35
+
36
+ .sideColor {
37
+ width: $side-color-width;
38
+ flex-shrink: 0;
39
+ background: var(--color-background-info-strong);
40
+
41
+ &.actionRequired {
42
+ background: var(--color-background-warning-strong);
43
+ }
44
+ }
45
+
46
+ .content {
47
+ display: flex;
48
+ flex-direction: column;
49
+ flex: 1;
50
+ min-width: 0;
51
+ }
52
+
53
+ .header {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: var(--spacing-3xs);
57
+ padding: var(--spacing-xs) var(--spacing-standard);
58
+ padding-bottom: var(--spacing-3xs);
59
+ }
60
+
61
+ .headerTop {
62
+ display: flex;
63
+ align-items: flex-start;
64
+ gap: var(--spacing-3xs);
65
+ }
66
+
67
+ .headerLeft {
68
+ display: flex;
69
+ flex-direction: column;
70
+ flex: 1;
71
+ gap: var(--spacing-sm);
72
+ min-width: 0;
73
+ }
74
+
75
+ .commentInfo {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: var(--spacing-xs);
79
+ }
80
+
81
+ .commentId {
82
+ font-size: var(--font-size-sm);
83
+ line-height: var(--line-height-sm);
84
+ color: var(--color-font-secondary);
85
+ }
86
+
87
+ .authorInfo {
88
+ display: flex;
89
+ align-items: center;
90
+ gap: var(--spacing-xs);
91
+ font-size: var(--font-size-sm);
92
+ line-height: var(--line-height-sm);
93
+ }
94
+
95
+ .authorName {
96
+ color: var(--color-font-main);
97
+ }
98
+
99
+ .timestamp {
100
+ color: var(--color-font-secondary);
101
+ }
102
+
103
+ .actions {
104
+ display: flex;
105
+ align-items: center;
106
+ gap: var(--spacing-3xs);
107
+ opacity: 0;
108
+ visibility: hidden;
109
+ transition:
110
+ opacity $transition-duration ease,
111
+ visibility $transition-duration ease;
112
+
113
+ button {
114
+ background: transparent !important;
115
+
116
+ > * {
117
+ background: transparent !important;
118
+ }
119
+
120
+ &:hover {
121
+ background: var(--color-background-action-hover-weak) !important;
122
+ background-color: var(--color-background-action-hover-weak) !important;
123
+
124
+ > * {
125
+ background: var(--color-background-action-hover-weak) !important;
126
+ background-color: var(--color-background-action-hover-weak) !important;
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ .body {
133
+ display: flex;
134
+ flex-direction: column;
135
+ gap: var(--spacing-3xs);
136
+ padding: var(--spacing-xs) var(--spacing-standard);
137
+ }
138
+
139
+ .message {
140
+ font-size: var(--font-size-xs);
141
+ line-height: var(--line-height-xs);
142
+ color: var(--color-font-main);
143
+ word-wrap: break-word;
144
+
145
+ &.truncated {
146
+ display: -webkit-box;
147
+ -webkit-line-clamp: $line-clamp-count;
148
+ -webkit-box-orient: vertical;
149
+ overflow: hidden;
150
+ text-overflow: ellipsis;
151
+ }
152
+ }
153
+
154
+ .replyCount {
155
+ font-size: var(--font-size-xs);
156
+ line-height: var(--line-height-xs);
157
+ color: var(--color-font-secondary);
158
+ }
159
+
160
+ .dropdownContent {
161
+ z-index: 1300 !important;
162
+ }
@@ -0,0 +1,171 @@
1
+ import classNames from 'classnames';
2
+ import styles from './ds-comment-card.module.scss';
3
+ import type { DsCommentCardProps } from './ds-comment-card.types';
4
+ import { DsAvatarGroup } from '../ds-avatar-group';
5
+ import { DsButton } from '../ds-button';
6
+ import { DsIcon } from '../ds-icon';
7
+ import { DsTag } from '../ds-tag';
8
+ import { DsDropdownMenu } from '../ds-dropdown-menu';
9
+ import { formatRelativeTime } from '../../utils/format-relative-time';
10
+
11
+ export const DsCommentCard = ({
12
+ ref,
13
+ comment,
14
+ disabled = false,
15
+ overflow = 'hidden',
16
+ onClick,
17
+ onResolve,
18
+ onToggleActionRequired,
19
+ onForward,
20
+ onMarkUnread,
21
+ onCopyLink,
22
+ onDelete,
23
+ formatTimestamp = formatRelativeTime,
24
+ className,
25
+ style,
26
+ }: DsCommentCardProps) => {
27
+ const { numericId, author, createdAt, messages, referenceTag, isActionRequired } = comment;
28
+
29
+ const initialMessage = messages.find((m) => m.isInitialMessage) || messages[0];
30
+ const replyCount = messages.length - 1;
31
+
32
+ const participants = Array.from(new Map(messages.map((m) => [m.author.id, m.author])).values());
33
+
34
+ const renderReferenceTag = () => {
35
+ if (!referenceTag) {
36
+ return null;
37
+ }
38
+
39
+ if (typeof referenceTag === 'string') {
40
+ return (
41
+ <DsTag
42
+ size="small"
43
+ label={referenceTag}
44
+ slots={{
45
+ icon: <DsIcon icon="sell" size="tiny" />,
46
+ }}
47
+ />
48
+ );
49
+ }
50
+
51
+ return referenceTag;
52
+ };
53
+
54
+ const avatars = participants.map((p) => ({
55
+ name: p.name,
56
+ src: p.avatarSrc,
57
+ }));
58
+
59
+ const handleResolve = (e: React.MouseEvent) => {
60
+ e.stopPropagation();
61
+ onResolve?.();
62
+ };
63
+
64
+ const showMoreMenu = onToggleActionRequired || onForward || onMarkUnread || onCopyLink || onDelete;
65
+
66
+ return (
67
+ <button
68
+ ref={ref}
69
+ type="button"
70
+ className={classNames(styles.card, className)}
71
+ style={style}
72
+ onClick={onClick}
73
+ disabled={disabled}
74
+ data-disabled={disabled ? '' : undefined}
75
+ aria-label={`Comment #${String(numericId)} by ${author.name}${isActionRequired ? ', action required' : ''}`}
76
+ >
77
+ <div
78
+ className={classNames(styles.sideColor, {
79
+ [styles.actionRequired]: isActionRequired,
80
+ })}
81
+ />
82
+
83
+ <div className={styles.content}>
84
+ <div className={styles.header}>
85
+ <div className={styles.headerTop}>
86
+ <div className={styles.headerLeft}>
87
+ <DsAvatarGroup avatars={avatars} size="sm" max={4} />
88
+
89
+ <div className={styles.commentInfo}>
90
+ <span className={styles.commentId}>#{numericId}</span>
91
+ {renderReferenceTag()}
92
+ </div>
93
+
94
+ <div className={styles.authorInfo}>
95
+ <span className={styles.authorName}>{author.name}</span>
96
+ <span className={styles.timestamp}>{formatTimestamp(createdAt)}</span>
97
+ </div>
98
+ </div>
99
+
100
+ <div className={styles.actions}>
101
+ {showMoreMenu && (
102
+ <DsDropdownMenu.Root>
103
+ <DsDropdownMenu.Trigger asChild>
104
+ <DsButton design="v1.2" buttonType="tertiary" size="small" aria-label="More actions">
105
+ <DsIcon icon="more_vert" size="tiny" />
106
+ </DsButton>
107
+ </DsDropdownMenu.Trigger>
108
+ <DsDropdownMenu.Content className={styles.dropdownContent}>
109
+ {onToggleActionRequired && (
110
+ <DsDropdownMenu.Item value="toggle-action" onClick={() => onToggleActionRequired()}>
111
+ {isActionRequired ? 'Remove action requirement' : 'Require action'}
112
+ </DsDropdownMenu.Item>
113
+ )}
114
+ {onForward && (
115
+ <DsDropdownMenu.Item value="forward" onClick={() => onForward()}>
116
+ Forward
117
+ </DsDropdownMenu.Item>
118
+ )}
119
+ {onMarkUnread && (
120
+ <DsDropdownMenu.Item value="mark-unread" onClick={() => onMarkUnread()}>
121
+ Mark as &apos;Unread&apos;
122
+ </DsDropdownMenu.Item>
123
+ )}
124
+ {onCopyLink && (
125
+ <DsDropdownMenu.Item value="copy-link" onClick={() => onCopyLink()}>
126
+ Copy link
127
+ </DsDropdownMenu.Item>
128
+ )}
129
+ {onDelete && (
130
+ <DsDropdownMenu.Item value="delete" onClick={() => onDelete()}>
131
+ Delete
132
+ </DsDropdownMenu.Item>
133
+ )}
134
+ </DsDropdownMenu.Content>
135
+ </DsDropdownMenu.Root>
136
+ )}
137
+
138
+ {onResolve && (
139
+ <DsButton
140
+ design="v1.2"
141
+ buttonType="tertiary"
142
+ size="small"
143
+ onClick={handleResolve}
144
+ aria-label="Resolve comment"
145
+ >
146
+ <DsIcon icon="check_circle" size="tiny" />
147
+ </DsButton>
148
+ )}
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <div className={styles.body}>
154
+ <p
155
+ className={classNames(styles.message, {
156
+ [styles.truncated]: overflow === 'hidden',
157
+ })}
158
+ >
159
+ {initialMessage?.content}
160
+ </p>
161
+
162
+ {replyCount > 0 && (
163
+ <span className={styles.replyCount}>
164
+ {replyCount} {replyCount === 1 ? 'reply' : 'replies'}
165
+ </span>
166
+ )}
167
+ </div>
168
+ </div>
169
+ </button>
170
+ );
171
+ };
@@ -0,0 +1,45 @@
1
+ import type { CSSProperties, ReactNode, Ref } from 'react';
2
+
3
+ export interface CommentAuthor {
4
+ id: string;
5
+ name: string;
6
+ avatarSrc?: string;
7
+ }
8
+
9
+ export interface CommentMessage {
10
+ id: string;
11
+ author: CommentAuthor;
12
+ content: string;
13
+ createdAt: Date;
14
+ isInitialMessage?: boolean;
15
+ isResolved?: boolean;
16
+ }
17
+
18
+ export interface CommentData {
19
+ id: string;
20
+ numericId: number;
21
+ author: CommentAuthor;
22
+ createdAt: Date;
23
+ isResolved: boolean;
24
+ messages: CommentMessage[];
25
+ labels?: string[];
26
+ referenceTag?: ReactNode;
27
+ isActionRequired?: boolean;
28
+ }
29
+
30
+ export interface DsCommentCardProps {
31
+ ref?: Ref<HTMLButtonElement>;
32
+ comment: CommentData;
33
+ disabled?: boolean;
34
+ overflow?: 'hidden' | 'displayed';
35
+ onClick?: () => void;
36
+ onResolve?: () => void;
37
+ onToggleActionRequired?: () => void;
38
+ onForward?: () => void;
39
+ onMarkUnread?: () => void;
40
+ onCopyLink?: () => void;
41
+ onDelete?: () => void;
42
+ formatTimestamp?: (date: Date) => string;
43
+ className?: string;
44
+ style?: CSSProperties;
45
+ }
@@ -0,0 +1,2 @@
1
+ export { DsCommentCard } from './ds-comment-card';
2
+ export * from './ds-comment-card.types';
@@ -0,0 +1,65 @@
1
+ $indicator-size: 32px;
2
+ $avatar-size: 20px;
3
+ $plus-icon-font-size: 20px;
4
+ $border-width: 1px;
5
+ $border-radius-circle: 50%;
6
+ $outline-width: 2px;
7
+ $outline-offset: 2px;
8
+ $transition-duration: 0.2s;
9
+
10
+ .indicator {
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ width: $indicator-size;
15
+ height: $indicator-size;
16
+ padding: 0;
17
+ border: $border-width solid var(--color-border);
18
+ border-radius: var(--spacing-standard) var(--spacing-standard) var(--spacing-standard) 0;
19
+ background: var(--color-background);
20
+ box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.15);
21
+ cursor: pointer;
22
+ transition:
23
+ background-color $transition-duration ease,
24
+ border-color $transition-duration ease;
25
+
26
+ &:focus-visible {
27
+ outline: $outline-width solid var(--color-focus);
28
+ outline-offset: $outline-offset;
29
+ }
30
+
31
+ &:not(.actionRequired):hover {
32
+ background: var(--color-background-secondary);
33
+ border-color: var(--color-border-action-secondary-hover);
34
+ }
35
+ }
36
+
37
+ .placeholder {
38
+ .plusIcon {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ width: $avatar-size;
43
+ height: $avatar-size;
44
+ font-size: $plus-icon-font-size;
45
+ font-weight: var(--font-weight-semi-bold);
46
+ line-height: 0;
47
+ color: var(--color-font-main);
48
+ }
49
+ }
50
+
51
+ .actionRequired {
52
+ background: var(--color-background-warning-strong);
53
+
54
+ &:hover {
55
+ background: var(--color-background-danger-strong-hover);
56
+ border-color: var(--color-border-action-secondary-hover);
57
+ }
58
+ }
59
+
60
+ .avatar {
61
+ width: $avatar-size;
62
+ height: $avatar-size;
63
+ border-radius: $border-radius-circle;
64
+ object-fit: cover;
65
+ }
@@ -0,0 +1,48 @@
1
+ import classNames from 'classnames';
2
+ import styles from './ds-comment-indicator.module.scss';
3
+ import type { DsCommentIndicatorProps } from './ds-comment-indicator.types';
4
+ import { DsAvatar } from '../ds-avatar';
5
+
6
+ export const DsCommentIndicator = ({
7
+ type = 'placeholder',
8
+ avatarSrc,
9
+ avatarName = '',
10
+ onClick,
11
+ ref,
12
+ className,
13
+ style,
14
+ }: DsCommentIndicatorProps) => {
15
+ const isPlaceholder = type === 'placeholder';
16
+ const isActionRequired = type === 'action-required';
17
+
18
+ const getAriaLabel = () => {
19
+ if (isPlaceholder) {
20
+ return 'Add comment';
21
+ }
22
+ return 'View comment';
23
+ };
24
+
25
+ return (
26
+ <button
27
+ ref={ref}
28
+ type="button"
29
+ className={classNames(
30
+ styles.indicator,
31
+ {
32
+ [styles.placeholder]: isPlaceholder,
33
+ [styles.actionRequired]: isActionRequired,
34
+ },
35
+ className,
36
+ )}
37
+ style={style}
38
+ onClick={onClick}
39
+ aria-label={getAriaLabel()}
40
+ >
41
+ {isPlaceholder ? (
42
+ <span className={styles.plusIcon}>+</span>
43
+ ) : (
44
+ <DsAvatar src={avatarSrc} name={avatarName} size="sm" className={styles.avatar} />
45
+ )}
46
+ </button>
47
+ );
48
+ };
@@ -0,0 +1,13 @@
1
+ import type { CSSProperties, Ref } from 'react';
2
+
3
+ export type CommentIndicatorType = 'placeholder' | 'default' | 'action-required';
4
+
5
+ export interface DsCommentIndicatorProps {
6
+ type?: CommentIndicatorType;
7
+ avatarSrc?: string;
8
+ avatarName?: string;
9
+ onClick?: () => void;
10
+ ref?: Ref<HTMLButtonElement>;
11
+ className?: string;
12
+ style?: CSSProperties;
13
+ }
@@ -0,0 +1,2 @@
1
+ export { DsCommentIndicator } from './ds-comment-indicator';
2
+ export * from './ds-comment-indicator.types';