@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.
- package/CHANGELOG.md +27 -0
- package/dist/index.cjs +12394 -8354
- package/dist/index.d.cts +806 -118
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +806 -118
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12376 -8357
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +25 -23
- package/src/components/ds-autocomplete/ds-autocomplete.module.scss +2 -1
- package/src/components/ds-autocomplete/ds-autocomplete.tsx +47 -40
- package/src/components/ds-autocomplete/ds-autocomplete.types.tsx +37 -14
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +3 -7
- package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.module.scss +62 -0
- package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.tsx +83 -0
- package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.types.ts +12 -0
- package/src/components/ds-comment-bubble/components/ds-comment-thread/index.ts +2 -0
- package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.module.scss +149 -0
- package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.tsx +147 -0
- package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.types.ts +15 -0
- package/src/components/ds-comment-bubble/components/ds-thread-item/index.ts +2 -0
- package/src/components/ds-comment-bubble/ds-comment-bubble.module.scss +298 -0
- package/src/components/ds-comment-bubble/ds-comment-bubble.tsx +241 -0
- package/src/components/ds-comment-bubble/ds-comment-bubble.types.ts +27 -0
- package/src/components/ds-comment-bubble/hooks/index.ts +3 -0
- package/src/components/ds-comment-bubble/hooks/use-auto-resize.ts +17 -0
- package/src/components/ds-comment-bubble/hooks/use-click-outside.ts +22 -0
- package/src/components/ds-comment-bubble/hooks/use-scroll-overflow.ts +27 -0
- package/src/components/ds-comment-bubble/index.ts +6 -0
- package/src/components/ds-comment-card/ds-comment-card.module.scss +162 -0
- package/src/components/ds-comment-card/ds-comment-card.tsx +171 -0
- package/src/components/ds-comment-card/ds-comment-card.types.ts +45 -0
- package/src/components/ds-comment-card/index.ts +2 -0
- package/src/components/ds-comment-indicator/ds-comment-indicator.module.scss +65 -0
- package/src/components/ds-comment-indicator/ds-comment-indicator.tsx +48 -0
- package/src/components/ds-comment-indicator/ds-comment-indicator.types.ts +13 -0
- package/src/components/ds-comment-indicator/index.ts +2 -0
- package/src/components/ds-comments-drawer/comments-filters.test.ts +483 -0
- package/src/components/ds-comments-drawer/comments-filters.ts +134 -0
- package/src/components/ds-comments-drawer/comments-filters.types.ts +14 -0
- package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.module.scss +92 -0
- package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx +219 -0
- package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.types.ts +12 -0
- package/src/components/ds-comments-drawer/components/comments-filter-modal/index.ts +2 -0
- package/src/components/ds-comments-drawer/ds-comments-drawer.module.scss +67 -0
- package/src/components/ds-comments-drawer/ds-comments-drawer.tsx +215 -0
- package/src/components/ds-comments-drawer/ds-comments-drawer.types.ts +23 -0
- package/src/components/ds-comments-drawer/index.ts +3 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +2 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +8 -4
- package/src/components/ds-file-upload/types/accept-types.ts +0 -1
- package/src/components/ds-filter-status-icon/ds-filter-status-icon.module.scss +19 -0
- package/src/components/ds-filter-status-icon/ds-filter-status-icon.tsx +35 -0
- package/src/components/ds-filter-status-icon/ds-filter-status-icon.types.ts +31 -0
- package/src/components/ds-filter-status-icon/index.ts +3 -0
- package/src/components/ds-icon/custom-icons/index.ts +10 -0
- package/src/components/ds-icon/custom-icons/special-failed.tsx +16 -0
- package/src/components/ds-icon/custom-icons/special-paused.tsx +16 -0
- package/src/components/ds-icon/custom-icons/special-running.tsx +16 -0
- package/src/components/ds-icon/custom-icons/special-warning.tsx +16 -0
- package/src/components/ds-panel/ds-panel.module.scss +3 -3
- package/src/components/ds-progress-arc/ds-progress-arc.module.scss +57 -0
- package/src/components/ds-progress-arc/ds-progress-arc.tsx +111 -0
- package/src/components/ds-progress-arc/ds-progress-arc.types.ts +34 -0
- package/src/components/ds-progress-arc/index.ts +3 -0
- package/src/components/ds-progress-arc/utils.test.ts +79 -0
- package/src/components/ds-progress-arc/utils.ts +61 -0
- package/src/components/ds-progress-donut/ds-progress-donut.module.scss +67 -0
- package/src/components/ds-progress-donut/ds-progress-donut.tsx +83 -0
- package/src/components/ds-progress-donut/ds-progress-donut.types.ts +38 -0
- package/src/components/ds-progress-donut/index.ts +2 -0
- package/src/components/ds-progress-donut/utils.ts +1 -0
- package/src/components/ds-progress-linear/ds-progress-linear.module.scss +100 -0
- package/src/components/ds-progress-linear/ds-progress-linear.tsx +107 -0
- package/src/components/ds-progress-linear/ds-progress-linear.types.ts +72 -0
- package/src/components/ds-progress-linear/index.ts +2 -0
- package/src/components/ds-progress-linear/utils.test.ts +70 -0
- package/src/components/ds-progress-linear/utils.ts +7 -0
- package/src/components/ds-radio-group/ds-radio-group.tsx +2 -0
- package/src/components/ds-select/ds-select.tsx +2 -1
- package/src/components/ds-select/ds-select.types.tsx +7 -0
- package/src/components/ds-stepper/components/ds-step.tsx +1 -1
- package/src/components/ds-stepper/ds-stepper.module.scss +16 -2
- package/src/components/ds-stepper/ds-stepper.tsx +3 -1
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +9 -0
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +137 -0
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +22 -0
- package/src/components/ds-table/components/ds-table-body-virtualized/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +3 -1
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +7 -10
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +3 -4
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +13 -53
- package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +32 -85
- package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +0 -6
- package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.module.scss +10 -0
- package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.tsx +45 -0
- package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.types.ts +7 -0
- package/src/components/ds-table/components/ds-table-row-expandable-cell/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.module.scss +6 -0
- package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.tsx +30 -0
- package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.types.ts +6 -0
- package/src/components/ds-table/components/ds-table-row-selectable-cell/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +80 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +95 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +11 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/index.ts +2 -0
- package/src/components/ds-table/context/ds-table-context.tsx +0 -9
- package/src/components/ds-table/ds-table.module.scss +4 -17
- package/src/components/ds-table/ds-table.tsx +44 -77
- package/src/components/ds-table/ds-table.types.ts +57 -12
- package/src/components/ds-table/index.ts +1 -0
- package/src/components/ds-table/styles/_mixins.scss +15 -0
- package/src/components/ds-table/styles/_variables.scss +2 -2
- package/src/components/ds-table/utils/{column-styling.ts → column-size.ts} +4 -14
- package/src/components/ds-table/utils/constants.ts +4 -0
- package/src/components/ds-tag/index.tsx +1 -0
- package/src/components/ds-tag-filter/ds-tag-filter.module.scss +13 -15
- package/src/components/ds-tag-filter/ds-tag-filter.tsx +38 -72
- package/src/components/ds-tag-filter/ds-tag-filter.types.ts +2 -3
- package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +12 -62
- package/src/components/ds-tag-filter/utils.test.ts +2 -108
- package/src/components/ds-tag-filter/utils.ts +2 -12
- package/src/components/ds-tree/ds-tree.module.scss +283 -0
- package/src/components/ds-tree/ds-tree.tsx +172 -0
- package/src/components/ds-tree/ds-tree.types.ts +83 -0
- package/src/components/ds-tree/ds-tree.utils.ts +11 -0
- package/src/components/ds-tree/index.ts +3 -0
- package/src/index.ts +9 -0
- package/src/utils/format-relative-time.test.ts +74 -0
- package/src/utils/format-relative-time.ts +52 -0
- package/src/utils/numbers.test.ts +24 -0
- package/src/utils/numbers.ts +1 -0
- package/src/components/ds-table/utils/create-columns-grid-template.ts +0 -23
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
.modal {
|
|
2
|
+
height: 600px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.header {
|
|
6
|
+
display: flex;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: var(--spacing-sm);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.headerLeft {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: var(--spacing-sm);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.body {
|
|
19
|
+
display: flex;
|
|
20
|
+
padding: 0;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.tabs {
|
|
25
|
+
display: flex;
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.tabList {
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
width: 200px;
|
|
33
|
+
border-right: 1px solid var(--color-border-secondary);
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
gap: var(--spacing-3xs);
|
|
37
|
+
padding: var(--spacing-sm);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.tabContent {
|
|
41
|
+
flex: 1;
|
|
42
|
+
overflow-y: auto;
|
|
43
|
+
padding: var(--spacing-standard);
|
|
44
|
+
|
|
45
|
+
&[hidden] {
|
|
46
|
+
display: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&[data-state='closed'] {
|
|
50
|
+
display: none;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.badge {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: var(--spacing-3xs);
|
|
58
|
+
margin-left: auto;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.dot {
|
|
62
|
+
width: 6px;
|
|
63
|
+
height: 6px;
|
|
64
|
+
border-radius: 50%;
|
|
65
|
+
background: var(--color-primary-500-default);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.filterSection {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
gap: var(--spacing-sm);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.dateField {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
gap: var(--spacing-xs);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.dateLabel {
|
|
81
|
+
display: block;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.emptyMessage {
|
|
85
|
+
color: var(--color-font-secondary);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.footer {
|
|
89
|
+
display: flex;
|
|
90
|
+
justify-content: space-between;
|
|
91
|
+
align-items: center;
|
|
92
|
+
}
|
package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import styles from './comments-filter-modal.module.scss';
|
|
3
|
+
import type { CommentsFilterModalProps } from './comments-filter-modal.types';
|
|
4
|
+
import { DsModal } from '../../../ds-modal';
|
|
5
|
+
import { DsTabs } from '../../../ds-tabs';
|
|
6
|
+
import { DsTypography } from '../../../ds-typography';
|
|
7
|
+
import { DsCheckbox } from '../../../ds-checkbox';
|
|
8
|
+
import { DsButton } from '../../../ds-button';
|
|
9
|
+
import { DsIcon } from '../../../ds-icon';
|
|
10
|
+
import { DsDateInput } from '../../../ds-date-input';
|
|
11
|
+
|
|
12
|
+
export const CommentsFilterModal = ({
|
|
13
|
+
open,
|
|
14
|
+
onOpenChange,
|
|
15
|
+
filters,
|
|
16
|
+
onFiltersChange,
|
|
17
|
+
onApply,
|
|
18
|
+
onClearAll,
|
|
19
|
+
availableAuthors,
|
|
20
|
+
availableLabels,
|
|
21
|
+
}: CommentsFilterModalProps) => {
|
|
22
|
+
const [selectedTab, setSelectedTab] = useState<string>('statuses');
|
|
23
|
+
|
|
24
|
+
const handleStatusToggle = (status: 'unresolved' | 'resolved' | 'action-required', checked: boolean) => {
|
|
25
|
+
onFiltersChange({
|
|
26
|
+
...filters,
|
|
27
|
+
statuses: checked ? [...filters.statuses, status] : filters.statuses.filter((s) => s !== status),
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleAuthorToggle = (authorId: string, checked: boolean) => {
|
|
32
|
+
onFiltersChange({
|
|
33
|
+
...filters,
|
|
34
|
+
authors: checked ? [...filters.authors, authorId] : filters.authors.filter((a) => a !== authorId),
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleLabelToggle = (label: string, checked: boolean) => {
|
|
39
|
+
onFiltersChange({
|
|
40
|
+
...filters,
|
|
41
|
+
labels: checked ? [...filters.labels, label] : filters.labels.filter((l) => l !== label),
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleDateFromChange = (value: string | undefined) => {
|
|
46
|
+
onFiltersChange({ ...filters, dateFrom: value });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleDateToChange = (value: string | undefined) => {
|
|
50
|
+
onFiltersChange({ ...filters, dateTo: value });
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const filterCounts = {
|
|
54
|
+
statuses: filters.statuses.length,
|
|
55
|
+
authors: filters.authors.length,
|
|
56
|
+
dateRange: (filters.dateFrom ? 1 : 0) + (filters.dateTo ? 1 : 0),
|
|
57
|
+
labels: filters.labels.length,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<DsModal open={open} onOpenChange={onOpenChange} className={styles.modal}>
|
|
62
|
+
<DsModal.Header className={styles.header}>
|
|
63
|
+
<div className={styles.headerLeft}>
|
|
64
|
+
<DsIcon icon="filter_list" size="small" />
|
|
65
|
+
<DsModal.Title>Filters</DsModal.Title>
|
|
66
|
+
</div>
|
|
67
|
+
<DsModal.CloseTrigger />
|
|
68
|
+
</DsModal.Header>
|
|
69
|
+
|
|
70
|
+
<DsModal.Body className={styles.body}>
|
|
71
|
+
<DsTabs.Root
|
|
72
|
+
value={selectedTab}
|
|
73
|
+
onValueChange={(value) => {
|
|
74
|
+
if (value) {
|
|
75
|
+
setSelectedTab(value);
|
|
76
|
+
}
|
|
77
|
+
}}
|
|
78
|
+
orientation="vertical"
|
|
79
|
+
className={styles.tabs}
|
|
80
|
+
>
|
|
81
|
+
<DsTabs.List className={styles.tabList}>
|
|
82
|
+
<DsTabs.Tab value="statuses">
|
|
83
|
+
<DsTypography variant="body-sm-md">Status</DsTypography>
|
|
84
|
+
{filterCounts.statuses > 0 && (
|
|
85
|
+
<div className={styles.badge}>
|
|
86
|
+
<span className={styles.dot} />
|
|
87
|
+
<DsTypography variant="body-sm-reg">{filterCounts.statuses}</DsTypography>
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</DsTabs.Tab>
|
|
91
|
+
<DsTabs.Tab value="authors">
|
|
92
|
+
<DsTypography variant="body-sm-md">Author</DsTypography>
|
|
93
|
+
{filterCounts.authors > 0 && (
|
|
94
|
+
<div className={styles.badge}>
|
|
95
|
+
<span className={styles.dot} />
|
|
96
|
+
<DsTypography variant="body-sm-reg">{filterCounts.authors}</DsTypography>
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</DsTabs.Tab>
|
|
100
|
+
<DsTabs.Tab value="dateRange">
|
|
101
|
+
<DsTypography variant="body-sm-md">Date range</DsTypography>
|
|
102
|
+
{filterCounts.dateRange > 0 && (
|
|
103
|
+
<div className={styles.badge}>
|
|
104
|
+
<span className={styles.dot} />
|
|
105
|
+
<DsTypography variant="body-sm-reg">{filterCounts.dateRange}</DsTypography>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
</DsTabs.Tab>
|
|
109
|
+
<DsTabs.Tab value="labels">
|
|
110
|
+
<DsTypography variant="body-sm-md">Labels</DsTypography>
|
|
111
|
+
{filterCounts.labels > 0 && (
|
|
112
|
+
<div className={styles.badge}>
|
|
113
|
+
<span className={styles.dot} />
|
|
114
|
+
<DsTypography variant="body-sm-reg">{filterCounts.labels}</DsTypography>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
</DsTabs.Tab>
|
|
118
|
+
</DsTabs.List>
|
|
119
|
+
|
|
120
|
+
<DsTabs.Content value="statuses" className={styles.tabContent}>
|
|
121
|
+
<div className={styles.filterSection}>
|
|
122
|
+
<DsCheckbox
|
|
123
|
+
label="Unresolved"
|
|
124
|
+
checked={filters.statuses.includes('unresolved')}
|
|
125
|
+
onCheckedChange={(checked) => handleStatusToggle('unresolved', checked === true)}
|
|
126
|
+
/>
|
|
127
|
+
<DsCheckbox
|
|
128
|
+
label="Resolved"
|
|
129
|
+
checked={filters.statuses.includes('resolved')}
|
|
130
|
+
onCheckedChange={(checked) => handleStatusToggle('resolved', checked === true)}
|
|
131
|
+
/>
|
|
132
|
+
<DsCheckbox
|
|
133
|
+
label="Action required"
|
|
134
|
+
checked={filters.statuses.includes('action-required')}
|
|
135
|
+
onCheckedChange={(checked) => handleStatusToggle('action-required', checked === true)}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
</DsTabs.Content>
|
|
139
|
+
<DsTabs.Content value="authors" className={styles.tabContent}>
|
|
140
|
+
<div className={styles.filterSection}>
|
|
141
|
+
{availableAuthors.length === 0 ? (
|
|
142
|
+
<DsTypography variant="body-sm-reg" className={styles.emptyMessage}>
|
|
143
|
+
No authors available
|
|
144
|
+
</DsTypography>
|
|
145
|
+
) : (
|
|
146
|
+
availableAuthors.map((author) => (
|
|
147
|
+
<DsCheckbox
|
|
148
|
+
key={author.id}
|
|
149
|
+
label={author.name}
|
|
150
|
+
checked={filters.authors.includes(author.id)}
|
|
151
|
+
onCheckedChange={(checked) => handleAuthorToggle(author.id, checked === true)}
|
|
152
|
+
/>
|
|
153
|
+
))
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
</DsTabs.Content>
|
|
157
|
+
<DsTabs.Content value="dateRange" className={styles.tabContent}>
|
|
158
|
+
<div className={styles.filterSection}>
|
|
159
|
+
<div className={styles.dateField}>
|
|
160
|
+
<DsTypography variant="body-sm-md" className={styles.dateLabel}>
|
|
161
|
+
From
|
|
162
|
+
</DsTypography>
|
|
163
|
+
<DsDateInput
|
|
164
|
+
value={filters.dateFrom}
|
|
165
|
+
onValueChange={handleDateFromChange}
|
|
166
|
+
max={filters.dateTo}
|
|
167
|
+
placeholder="Select start date"
|
|
168
|
+
disablePortal
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
<div className={styles.dateField}>
|
|
172
|
+
<DsTypography variant="body-sm-md" className={styles.dateLabel}>
|
|
173
|
+
To
|
|
174
|
+
</DsTypography>
|
|
175
|
+
<DsDateInput
|
|
176
|
+
value={filters.dateTo}
|
|
177
|
+
onValueChange={handleDateToChange}
|
|
178
|
+
min={filters.dateFrom}
|
|
179
|
+
placeholder="Select end date"
|
|
180
|
+
disablePortal
|
|
181
|
+
/>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</DsTabs.Content>
|
|
185
|
+
<DsTabs.Content value="labels" className={styles.tabContent}>
|
|
186
|
+
<div className={styles.filterSection}>
|
|
187
|
+
{availableLabels.length === 0 ? (
|
|
188
|
+
<DsTypography variant="body-sm-reg" className={styles.emptyMessage}>
|
|
189
|
+
No labels available
|
|
190
|
+
</DsTypography>
|
|
191
|
+
) : (
|
|
192
|
+
availableLabels.map((label) => (
|
|
193
|
+
<DsCheckbox
|
|
194
|
+
key={label}
|
|
195
|
+
label={label}
|
|
196
|
+
checked={filters.labels.includes(label)}
|
|
197
|
+
onCheckedChange={(checked) => handleLabelToggle(label, checked === true)}
|
|
198
|
+
/>
|
|
199
|
+
))
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
</DsTabs.Content>
|
|
203
|
+
</DsTabs.Root>
|
|
204
|
+
</DsModal.Body>
|
|
205
|
+
|
|
206
|
+
<DsModal.Footer className={styles.footer}>
|
|
207
|
+
<DsButton design="v1.2" variant="filled" buttonType="secondary" onClick={onClearAll}>
|
|
208
|
+
<DsIcon icon="close" size="tiny" />
|
|
209
|
+
Clear all
|
|
210
|
+
</DsButton>
|
|
211
|
+
<DsModal.Actions>
|
|
212
|
+
<DsButton design="v1.2" variant="filled" buttonType="primary" onClick={onApply}>
|
|
213
|
+
Apply
|
|
214
|
+
</DsButton>
|
|
215
|
+
</DsModal.Actions>
|
|
216
|
+
</DsModal.Footer>
|
|
217
|
+
</DsModal>
|
|
218
|
+
);
|
|
219
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CommentsFilterState } from '../../comments-filters.types';
|
|
2
|
+
|
|
3
|
+
export interface CommentsFilterModalProps {
|
|
4
|
+
open: boolean;
|
|
5
|
+
onOpenChange: (open: boolean) => void;
|
|
6
|
+
filters: CommentsFilterState;
|
|
7
|
+
onFiltersChange: (filters: CommentsFilterState) => void;
|
|
8
|
+
onApply: () => void;
|
|
9
|
+
onClearAll: () => void;
|
|
10
|
+
availableAuthors: Array<{ id: string; name: string }>;
|
|
11
|
+
availableLabels: string[];
|
|
12
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
$divider-width: 1px;
|
|
2
|
+
$divider-height: 16px;
|
|
3
|
+
$scrollbar-width: 8px;
|
|
4
|
+
$scrollbar-border-radius-pill: 9999px;
|
|
5
|
+
|
|
6
|
+
.headerActions {
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: var(--spacing-xs);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.divider {
|
|
13
|
+
width: $divider-width;
|
|
14
|
+
height: $divider-height;
|
|
15
|
+
background: var(--color-border-secondary);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.searchInput {
|
|
19
|
+
flex: 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.body {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: var(--spacing-standard);
|
|
26
|
+
padding: var(--spacing-standard);
|
|
27
|
+
overflow-y: auto;
|
|
28
|
+
|
|
29
|
+
&::-webkit-scrollbar {
|
|
30
|
+
width: $scrollbar-width;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&::-webkit-scrollbar-track {
|
|
34
|
+
background: transparent;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&::-webkit-scrollbar-thumb {
|
|
38
|
+
background: var(--color-background-tertiary);
|
|
39
|
+
border-radius: $scrollbar-border-radius-pill;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.emptyState {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
padding: var(--spacing-xl);
|
|
49
|
+
text-align: center;
|
|
50
|
+
color: var(--color-font-secondary);
|
|
51
|
+
font-size: var(--font-size-sm);
|
|
52
|
+
line-height: var(--line-height-sm);
|
|
53
|
+
gap: var(--spacing-sm);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.emptyIcon {
|
|
57
|
+
color: var(--color-icon-secondary);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.filterTagsToolbar {
|
|
61
|
+
min-width: 0;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.tagFilter {
|
|
66
|
+
min-width: 0;
|
|
67
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import styles from './ds-comments-drawer.module.scss';
|
|
3
|
+
import type { DsCommentsDrawerProps } from './ds-comments-drawer.types';
|
|
4
|
+
import { DsDrawer } from '../ds-drawer';
|
|
5
|
+
import { DsCommentCard } from '../ds-comment-card';
|
|
6
|
+
import { DsButton } from '../ds-button';
|
|
7
|
+
import { DsIcon } from '../ds-icon';
|
|
8
|
+
import { DsTextInput } from '../ds-text-input';
|
|
9
|
+
import { DsTagFilter, type TagFilterItem } from '../ds-tag-filter';
|
|
10
|
+
import {
|
|
11
|
+
type CommentsFilterState,
|
|
12
|
+
initialFilterState,
|
|
13
|
+
applyFilters,
|
|
14
|
+
filtersToTags,
|
|
15
|
+
removeTag,
|
|
16
|
+
} from './comments-filters';
|
|
17
|
+
import { CommentsFilterModal } from './components/comments-filter-modal';
|
|
18
|
+
|
|
19
|
+
export const DsCommentsDrawer = ({
|
|
20
|
+
open,
|
|
21
|
+
onOpenChange,
|
|
22
|
+
comments,
|
|
23
|
+
showResolved = false,
|
|
24
|
+
onShowResolvedChange,
|
|
25
|
+
searchQuery = '',
|
|
26
|
+
onSearchChange,
|
|
27
|
+
onCommentClick,
|
|
28
|
+
onResolveComment,
|
|
29
|
+
onToggleActionRequired,
|
|
30
|
+
onForward,
|
|
31
|
+
onMarkUnread,
|
|
32
|
+
onCopyLink,
|
|
33
|
+
onDelete,
|
|
34
|
+
noCommentsMessage,
|
|
35
|
+
className,
|
|
36
|
+
style,
|
|
37
|
+
}: DsCommentsDrawerProps) => {
|
|
38
|
+
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
|
|
39
|
+
const [pendingFilters, setPendingFilters] = useState<CommentsFilterState>(initialFilterState);
|
|
40
|
+
const [appliedFilters, setAppliedFilters] = useState<CommentsFilterState>(initialFilterState);
|
|
41
|
+
|
|
42
|
+
const authorsMap = new Map<string, string>();
|
|
43
|
+
const labelsSet = new Set<string>();
|
|
44
|
+
|
|
45
|
+
comments.forEach((comment) => {
|
|
46
|
+
authorsMap.set(comment.author.id, comment.author.name);
|
|
47
|
+
comment.labels?.forEach((label) => labelsSet.add(label));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const availableAuthors = Array.from(authorsMap.entries()).map(([id, name]) => ({ id, name }));
|
|
51
|
+
const availableLabels = Array.from(labelsSet);
|
|
52
|
+
|
|
53
|
+
let filteredComments = comments;
|
|
54
|
+
|
|
55
|
+
if (!showResolved) {
|
|
56
|
+
filteredComments = filteredComments.filter((c) => !c.isResolved);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
filteredComments = applyFilters(
|
|
60
|
+
filteredComments,
|
|
61
|
+
appliedFilters,
|
|
62
|
+
(comment) => comment.isActionRequired || false,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (searchQuery) {
|
|
66
|
+
const query = searchQuery.toLowerCase();
|
|
67
|
+
filteredComments = filteredComments.filter((comment) => {
|
|
68
|
+
const matchesContent = comment.messages.some((m) => m.content.toLowerCase().includes(query));
|
|
69
|
+
const matchesAuthor = comment.author.name.toLowerCase().includes(query);
|
|
70
|
+
const matchesId = `#${String(comment.numericId)}`.includes(query);
|
|
71
|
+
return matchesContent || matchesAuthor || matchesId;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const commentCount = comments.filter((c) => !c.isResolved).length;
|
|
76
|
+
const resolvedCount = comments.filter((c) => c.isResolved).length;
|
|
77
|
+
const hasResolved = resolvedCount > 0;
|
|
78
|
+
|
|
79
|
+
const filterTags = filtersToTags(appliedFilters, authorsMap);
|
|
80
|
+
|
|
81
|
+
const handleShowResolvedToggle = () => {
|
|
82
|
+
onShowResolvedChange?.(!showResolved);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleFilterClick = () => {
|
|
86
|
+
setPendingFilters(appliedFilters);
|
|
87
|
+
setIsFilterModalOpen(true);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleApplyFilters = () => {
|
|
91
|
+
setAppliedFilters(pendingFilters);
|
|
92
|
+
setIsFilterModalOpen(false);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleClearAllFilters = () => {
|
|
96
|
+
setPendingFilters(initialFilterState);
|
|
97
|
+
setAppliedFilters(initialFilterState);
|
|
98
|
+
setIsFilterModalOpen(false);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleDeleteTag = (item: TagFilterItem) => {
|
|
102
|
+
const tag = filterTags.find((c) => c.id === item.id);
|
|
103
|
+
if (tag) {
|
|
104
|
+
const newFilters = removeTag(appliedFilters, tag);
|
|
105
|
+
setAppliedFilters(newFilters);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
<DsDrawer
|
|
112
|
+
open={open}
|
|
113
|
+
onOpenChange={onOpenChange}
|
|
114
|
+
position="start"
|
|
115
|
+
columns={4}
|
|
116
|
+
className={className}
|
|
117
|
+
style={style}
|
|
118
|
+
>
|
|
119
|
+
<DsDrawer.Header>
|
|
120
|
+
<DsDrawer.Title>{commentCount} Comments</DsDrawer.Title>
|
|
121
|
+
|
|
122
|
+
<div className={styles.headerActions}>
|
|
123
|
+
<DsButton design="v1.2" buttonType="tertiary" size="small" aria-label="More actions">
|
|
124
|
+
<DsIcon icon="more_vert" size="tiny" />
|
|
125
|
+
</DsButton>
|
|
126
|
+
|
|
127
|
+
<div className={styles.divider} />
|
|
128
|
+
|
|
129
|
+
<DsDrawer.CloseTrigger />
|
|
130
|
+
</div>
|
|
131
|
+
</DsDrawer.Header>
|
|
132
|
+
|
|
133
|
+
<DsDrawer.Toolbar>
|
|
134
|
+
<DsTextInput
|
|
135
|
+
placeholder="Search"
|
|
136
|
+
value={searchQuery}
|
|
137
|
+
onValueChange={onSearchChange}
|
|
138
|
+
className={styles.searchInput}
|
|
139
|
+
slots={{
|
|
140
|
+
startAdornment: <DsIcon icon="search" size="tiny" />,
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
<DsButton
|
|
145
|
+
design="v1.2"
|
|
146
|
+
buttonType="tertiary"
|
|
147
|
+
size="small"
|
|
148
|
+
aria-label="Filter"
|
|
149
|
+
onClick={handleFilterClick}
|
|
150
|
+
>
|
|
151
|
+
<DsIcon icon="filter_list" size="tiny" />
|
|
152
|
+
</DsButton>
|
|
153
|
+
</DsDrawer.Toolbar>
|
|
154
|
+
|
|
155
|
+
{hasResolved && (
|
|
156
|
+
<DsDrawer.Toolbar>
|
|
157
|
+
<DsButton design="v1.2" buttonType="tertiary" size="small" onClick={handleShowResolvedToggle}>
|
|
158
|
+
{showResolved ? 'Hide' : 'Show'} resolved ({resolvedCount})
|
|
159
|
+
</DsButton>
|
|
160
|
+
</DsDrawer.Toolbar>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{filterTags.length > 0 && (
|
|
164
|
+
<DsDrawer.Toolbar className={styles.filterTagsToolbar}>
|
|
165
|
+
<DsTagFilter
|
|
166
|
+
items={filterTags}
|
|
167
|
+
onClearAll={handleClearAllFilters}
|
|
168
|
+
onItemDelete={handleDeleteTag}
|
|
169
|
+
className={styles.tagFilter}
|
|
170
|
+
/>
|
|
171
|
+
</DsDrawer.Toolbar>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
<DsDrawer.Body className={styles.body}>
|
|
175
|
+
{filteredComments.length === 0 ? (
|
|
176
|
+
<div className={styles.emptyState}>
|
|
177
|
+
{noCommentsMessage || (
|
|
178
|
+
<>
|
|
179
|
+
<DsIcon icon="chat_bubble_outline" size="large" className={styles.emptyIcon} />
|
|
180
|
+
<span>No comments yet</span>
|
|
181
|
+
</>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
) : (
|
|
185
|
+
filteredComments.map((comment) => (
|
|
186
|
+
<DsCommentCard
|
|
187
|
+
key={comment.id}
|
|
188
|
+
comment={comment}
|
|
189
|
+
overflow="displayed"
|
|
190
|
+
onClick={() => onCommentClick?.(comment)}
|
|
191
|
+
onResolve={() => onResolveComment?.(comment.id)}
|
|
192
|
+
onToggleActionRequired={() => onToggleActionRequired?.(comment.id)}
|
|
193
|
+
onForward={() => onForward?.(comment.id)}
|
|
194
|
+
onMarkUnread={() => onMarkUnread?.(comment.id)}
|
|
195
|
+
onCopyLink={() => onCopyLink?.(comment.id)}
|
|
196
|
+
onDelete={() => onDelete?.(comment.id)}
|
|
197
|
+
/>
|
|
198
|
+
))
|
|
199
|
+
)}
|
|
200
|
+
</DsDrawer.Body>
|
|
201
|
+
</DsDrawer>
|
|
202
|
+
|
|
203
|
+
<CommentsFilterModal
|
|
204
|
+
open={isFilterModalOpen}
|
|
205
|
+
onOpenChange={setIsFilterModalOpen}
|
|
206
|
+
filters={pendingFilters}
|
|
207
|
+
onFiltersChange={setPendingFilters}
|
|
208
|
+
onApply={handleApplyFilters}
|
|
209
|
+
onClearAll={handleClearAllFilters}
|
|
210
|
+
availableAuthors={availableAuthors}
|
|
211
|
+
availableLabels={availableLabels}
|
|
212
|
+
/>
|
|
213
|
+
</>
|
|
214
|
+
);
|
|
215
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode, Ref } from 'react';
|
|
2
|
+
import type { CommentData } from '../ds-comment-card';
|
|
3
|
+
|
|
4
|
+
export interface DsCommentsDrawerProps {
|
|
5
|
+
ref?: Ref<HTMLElement>;
|
|
6
|
+
open: boolean;
|
|
7
|
+
onOpenChange: (open: boolean) => void;
|
|
8
|
+
comments: CommentData[];
|
|
9
|
+
showResolved?: boolean;
|
|
10
|
+
onShowResolvedChange?: (show: boolean) => void;
|
|
11
|
+
searchQuery?: string;
|
|
12
|
+
onSearchChange?: (query: string) => void;
|
|
13
|
+
onCommentClick?: (comment: CommentData) => void;
|
|
14
|
+
onResolveComment?: (commentId: string) => void;
|
|
15
|
+
onToggleActionRequired?: (commentId: string) => void;
|
|
16
|
+
onForward?: (commentId: string) => void;
|
|
17
|
+
onMarkUnread?: (commentId: string) => void;
|
|
18
|
+
onCopyLink?: (commentId: string) => void;
|
|
19
|
+
onDelete?: (commentId: string) => void;
|
|
20
|
+
noCommentsMessage?: ReactNode;
|
|
21
|
+
className?: string;
|
|
22
|
+
style?: CSSProperties;
|
|
23
|
+
}
|
|
@@ -284,6 +284,7 @@ const TriggerItem: React.FC<DsDropdownMenuTriggerItemProps> = ({ className, styl
|
|
|
284
284
|
* Use compound component pattern instead
|
|
285
285
|
* @deprecated
|
|
286
286
|
*/
|
|
287
|
+
/* c8 ignore start */
|
|
287
288
|
export const DsDropdownMenuLegacy: React.FC<DsDropdownMenuLegacyProps> = ({
|
|
288
289
|
options,
|
|
289
290
|
children,
|
|
@@ -365,6 +366,7 @@ export const DsDropdownMenuLegacy: React.FC<DsDropdownMenuLegacyProps> = ({
|
|
|
365
366
|
</DropdownMenu.Root>
|
|
366
367
|
);
|
|
367
368
|
};
|
|
369
|
+
/* c8 ignore stop */
|
|
368
370
|
|
|
369
371
|
/**
|
|
370
372
|
* Design system DsDropdownMenu component
|
|
@@ -13,8 +13,10 @@ export function DsExpandableTextInput({
|
|
|
13
13
|
icon,
|
|
14
14
|
...props
|
|
15
15
|
}: DsExpandableTextInputProps) {
|
|
16
|
-
const
|
|
17
|
-
|
|
16
|
+
const hasInitialValue = Boolean(props.value ?? props.defaultValue);
|
|
17
|
+
|
|
18
|
+
const [expanded, _setExpanded] = useState(hasInitialValue);
|
|
19
|
+
const [dirty, setDirty] = useState(hasInitialValue);
|
|
18
20
|
const ref = useRef<HTMLInputElement>(null);
|
|
19
21
|
|
|
20
22
|
const setExpanded = (newExpanded: boolean) => {
|
|
@@ -41,12 +43,14 @@ export function DsExpandableTextInput({
|
|
|
41
43
|
onChange={(e) => {
|
|
42
44
|
props.onChange?.(e);
|
|
43
45
|
|
|
44
|
-
setDirty(
|
|
46
|
+
setDirty(Boolean(e.target.value));
|
|
45
47
|
}}
|
|
46
48
|
onBlur={(e) => {
|
|
47
49
|
props.onBlur?.(e);
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
const currentValue = props.value ?? ref.current?.value;
|
|
52
|
+
|
|
53
|
+
if (!currentValue) {
|
|
50
54
|
setExpanded(false);
|
|
51
55
|
}
|
|
52
56
|
}}
|