@drivenets/design-system 0.6.1 → 0.7.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 (138) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/index.cjs +12297 -8255
  3. package/dist/index.d.cts +817 -118
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +817 -118
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +12280 -8259
  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-date-input/ds-date-input.tsx +3 -1
  51. package/src/components/ds-date-input/ds-date-input.types.tsx +6 -0
  52. package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +2 -0
  53. package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +8 -4
  54. package/src/components/ds-file-upload/components/file-upload/file-upload.tsx +2 -1
  55. package/src/components/ds-file-upload/components/file-upload/file-upload.types.tsx +5 -0
  56. package/src/components/ds-file-upload/types/accept-types.ts +0 -1
  57. package/src/components/ds-filter-status-icon/ds-filter-status-icon.module.scss +19 -0
  58. package/src/components/ds-filter-status-icon/ds-filter-status-icon.tsx +35 -0
  59. package/src/components/ds-filter-status-icon/ds-filter-status-icon.types.ts +31 -0
  60. package/src/components/ds-filter-status-icon/index.ts +3 -0
  61. package/src/components/ds-icon/custom-icons/index.ts +10 -0
  62. package/src/components/ds-icon/custom-icons/special-failed.tsx +16 -0
  63. package/src/components/ds-icon/custom-icons/special-paused.tsx +16 -0
  64. package/src/components/ds-icon/custom-icons/special-running.tsx +16 -0
  65. package/src/components/ds-icon/custom-icons/special-warning.tsx +16 -0
  66. package/src/components/ds-panel/ds-panel.module.scss +3 -3
  67. package/src/components/ds-progress-arc/ds-progress-arc.module.scss +57 -0
  68. package/src/components/ds-progress-arc/ds-progress-arc.tsx +111 -0
  69. package/src/components/ds-progress-arc/ds-progress-arc.types.ts +34 -0
  70. package/src/components/ds-progress-arc/index.ts +3 -0
  71. package/src/components/ds-progress-arc/utils.test.ts +79 -0
  72. package/src/components/ds-progress-arc/utils.ts +61 -0
  73. package/src/components/ds-progress-donut/ds-progress-donut.module.scss +67 -0
  74. package/src/components/ds-progress-donut/ds-progress-donut.tsx +83 -0
  75. package/src/components/ds-progress-donut/ds-progress-donut.types.ts +38 -0
  76. package/src/components/ds-progress-donut/index.ts +2 -0
  77. package/src/components/ds-progress-donut/utils.ts +1 -0
  78. package/src/components/ds-progress-linear/ds-progress-linear.module.scss +100 -0
  79. package/src/components/ds-progress-linear/ds-progress-linear.tsx +107 -0
  80. package/src/components/ds-progress-linear/ds-progress-linear.types.ts +72 -0
  81. package/src/components/ds-progress-linear/index.ts +2 -0
  82. package/src/components/ds-progress-linear/utils.test.ts +70 -0
  83. package/src/components/ds-progress-linear/utils.ts +7 -0
  84. package/src/components/ds-radio-group/ds-radio-group.tsx +2 -0
  85. package/src/components/ds-select/ds-select.tsx +2 -1
  86. package/src/components/ds-select/ds-select.types.tsx +7 -0
  87. package/src/components/ds-stepper/components/ds-step.tsx +1 -1
  88. package/src/components/ds-stepper/ds-stepper.module.scss +16 -2
  89. package/src/components/ds-stepper/ds-stepper.tsx +3 -1
  90. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +9 -0
  91. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +137 -0
  92. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +22 -0
  93. package/src/components/ds-table/components/ds-table-body-virtualized/index.ts +2 -0
  94. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +3 -1
  95. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +7 -10
  96. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +3 -4
  97. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +13 -53
  98. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +32 -85
  99. package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +0 -6
  100. package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.module.scss +10 -0
  101. package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.tsx +45 -0
  102. package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.types.ts +7 -0
  103. package/src/components/ds-table/components/ds-table-row-expandable-cell/index.ts +2 -0
  104. package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.module.scss +6 -0
  105. package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.tsx +30 -0
  106. package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.types.ts +6 -0
  107. package/src/components/ds-table/components/ds-table-row-selectable-cell/index.ts +2 -0
  108. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +80 -0
  109. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +95 -0
  110. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +11 -0
  111. package/src/components/ds-table/components/ds-table-row-virtualized/index.ts +2 -0
  112. package/src/components/ds-table/context/ds-table-context.tsx +0 -9
  113. package/src/components/ds-table/ds-table.module.scss +4 -17
  114. package/src/components/ds-table/ds-table.tsx +44 -77
  115. package/src/components/ds-table/ds-table.types.ts +57 -12
  116. package/src/components/ds-table/index.ts +1 -0
  117. package/src/components/ds-table/styles/_mixins.scss +15 -0
  118. package/src/components/ds-table/styles/_variables.scss +2 -2
  119. package/src/components/ds-table/utils/{column-styling.ts → column-size.ts} +4 -14
  120. package/src/components/ds-table/utils/constants.ts +4 -0
  121. package/src/components/ds-tag/index.tsx +1 -0
  122. package/src/components/ds-tag-filter/ds-tag-filter.module.scss +13 -15
  123. package/src/components/ds-tag-filter/ds-tag-filter.tsx +38 -72
  124. package/src/components/ds-tag-filter/ds-tag-filter.types.ts +2 -3
  125. package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +12 -62
  126. package/src/components/ds-tag-filter/utils.test.ts +2 -108
  127. package/src/components/ds-tag-filter/utils.ts +2 -12
  128. package/src/components/ds-tree/ds-tree.module.scss +283 -0
  129. package/src/components/ds-tree/ds-tree.tsx +172 -0
  130. package/src/components/ds-tree/ds-tree.types.ts +83 -0
  131. package/src/components/ds-tree/ds-tree.utils.ts +11 -0
  132. package/src/components/ds-tree/index.ts +3 -0
  133. package/src/index.ts +9 -0
  134. package/src/utils/format-relative-time.test.ts +74 -0
  135. package/src/utils/format-relative-time.ts +52 -0
  136. package/src/utils/numbers.test.ts +24 -0
  137. package/src/utils/numbers.ts +1 -0
  138. package/src/components/ds-table/utils/create-columns-grid-template.ts +0 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drivenets/design-system",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.cts",
@@ -36,7 +36,7 @@
36
36
  "directory": "/packages/design-system"
37
37
  },
38
38
  "dependencies": {
39
- "@ark-ui/react": "^5.31.0",
39
+ "@ark-ui/react": "^5.34.0",
40
40
  "@dnd-kit/core": "^6.3.1",
41
41
  "@dnd-kit/modifiers": "^9.0.0",
42
42
  "@dnd-kit/sortable": "^10.0.0",
@@ -51,41 +51,43 @@
51
51
  "@radix-ui/react-tooltip": "^1.2.8",
52
52
  "@radix-ui/react-visually-hidden": "^1.2.4",
53
53
  "@tanstack/react-table": "^8.21.3",
54
- "@tanstack/react-virtual": "^3.13.18",
55
- "@zag-js/react": "^1.33.1",
56
- "@zag-js/steps": "^1.33.1",
54
+ "@tanstack/react-virtual": "^3.13.19",
55
+ "@zag-js/react": "^1.35.2",
56
+ "@zag-js/steps": "^1.35.2",
57
57
  "classnames": "^2.5.1"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@internationalized/date": "^3.0.0",
61
+ "@tanstack/react-router": "^1.0.0",
61
62
  "react": "^19",
62
63
  "react-dom": "^19"
63
64
  },
64
65
  "devDependencies": {
65
66
  "@arethetypeswrong/cli": "^0.18.2",
66
- "@faker-js/faker": "^9.9.0",
67
+ "@eslint/compat": "^2.0.2",
68
+ "@faker-js/faker": "^10.3.0",
67
69
  "@hookform/resolvers": "^5.2.2",
68
70
  "@rollup/plugin-babel": "^6.1.0",
69
- "@storybook/addon-a11y": "^10.2.7",
70
- "@storybook/addon-docs": "^10.2.7",
71
- "@storybook/addon-vitest": "^10.2.7",
72
- "@storybook/builder-vite": "^10.2.7",
73
- "@storybook/react-vite": "^10.2.7",
74
- "@tanstack/react-query": "^5.90.20",
75
- "@tanstack/react-router": "^1.158.4",
71
+ "@storybook/addon-a11y": "^10.2.13",
72
+ "@storybook/addon-docs": "^10.2.13",
73
+ "@storybook/addon-vitest": "^10.2.13",
74
+ "@storybook/builder-vite": "^10.2.13",
75
+ "@storybook/react-vite": "^10.2.13",
76
+ "@tanstack/react-query": "^5.90.21",
77
+ "@tanstack/react-router": "^1.163.3",
76
78
  "@types/eslint-plugin-jsx-a11y": "^6.10.1",
77
- "@types/react": "^19.2.13",
79
+ "@types/react": "^19.2.14",
78
80
  "@types/react-dom": "^19.2.3",
79
81
  "@vitest/browser": "^4.0.18",
80
82
  "@vitest/browser-playwright": "^4.0.18",
81
83
  "@vitest/coverage-v8": "^4.0.18",
82
84
  "babel-plugin-react-compiler": "^1.0.0",
83
- "eslint": "^9.39.2",
85
+ "eslint": "^10.0.1",
84
86
  "eslint-plugin-jsx-a11y": "^6.10.2",
85
87
  "eslint-plugin-react": "^7.37.5",
86
88
  "eslint-plugin-react-hooks": "^7.0.1",
87
- "eslint-plugin-storybook": "^10.2.7",
88
- "jsdom": "^25.0.1",
89
+ "eslint-plugin-storybook": "^10.2.13",
90
+ "jsdom": "^28.1.0",
89
91
  "mockdate": "^3.0.5",
90
92
  "playwright": "^1.58.2",
91
93
  "postcss": "^8.5.6",
@@ -93,26 +95,26 @@
93
95
  "publint": "^0.3.17",
94
96
  "react": "^19.2.4",
95
97
  "react-dom": "^19.2.4",
96
- "react-hook-form": "^7.71.1",
98
+ "react-hook-form": "^7.71.2",
97
99
  "rollup-plugin-sass": "^1.15.3",
98
100
  "sass-embedded": "^1.97.3",
99
- "storybook": "^10.2.7",
101
+ "storybook": "^10.2.13",
100
102
  "tsdown": "^0.20.3",
101
103
  "typed-scss-modules": "^8.1.1",
102
104
  "typescript": "^5.9.3",
103
105
  "typescript-plugin-css-modules": "^5.2.0",
104
106
  "vite": "^7.3.1",
105
107
  "vitest": "^4.0.18",
106
- "zod": "^3.25.76",
108
+ "zod": "^4.3.6",
107
109
  "@drivenets/vite-plugin-design-system": "0.0.6"
108
110
  },
109
111
  "scripts": {
110
- "lint": "pnpm generate-scss-dts; eslint --max-warnings=0 .",
111
- "typecheck": "pnpm generate-scss-dts; tsc",
112
+ "lint": "pnpm generate-scss-dts && eslint --max-warnings=0 .; EXIT_CODE=$?; rm -rf .scss-dts; exit $EXIT_CODE",
113
+ "typecheck": "pnpm generate-scss-dts && tsc; EXIT_CODE=$?; rm -rf .scss-dts; exit $EXIT_CODE",
112
114
  "generate-scss-dts": "typed-scss-modules ./src -o ./.scss-dts --nameFormat all --exportType default > /dev/null 2>&1",
113
115
  "storybook": "storybook dev -p 6006",
114
116
  "test": "vitest --project=\"!requires-build\"",
115
- "test:coverage": "vitest --coverage",
117
+ "test:coverage": "pnpm test --coverage",
116
118
  "test:requires-build": "vitest --project=requires-build",
117
119
  "build": "tsdown",
118
120
  "build:watch": "pnpm build --watch",
@@ -189,7 +189,8 @@ $autocomplete-opacity-visible: 1;
189
189
  }
190
190
  }
191
191
 
192
- .noMatches {
192
+ .noMatches,
193
+ .loading {
193
194
  padding: var(--spacing-xs);
194
195
  color: var(--color-font-disabled);
195
196
  text-align: center;
@@ -1,15 +1,17 @@
1
- import { Combobox, useListCollection } from '@ark-ui/react/combobox';
1
+ import { useState } from 'react';
2
+ import { Combobox, createListCollection } from '@ark-ui/react/combobox';
2
3
  import { useFilter } from '@ark-ui/react/locale';
3
4
  import { Highlight } from '@ark-ui/react/highlight';
4
5
  import { Portal } from '@ark-ui/react/portal';
5
6
  import classNames from 'classnames';
6
7
  import styles from './ds-autocomplete.module.scss';
7
- import type { DsAutocompleteProps } from './ds-autocomplete.types';
8
+ import type { DsAutocompleteOption, DsAutocompleteProps } from './ds-autocomplete.types';
8
9
  import { DsIcon } from '../ds-icon';
9
10
 
10
11
  export const DsAutocomplete = ({
11
12
  id,
12
- options,
13
+ options = [],
14
+ loading = false,
13
15
  style,
14
16
  className,
15
17
  placeholder = 'Start typing to search...',
@@ -17,36 +19,42 @@ export const DsAutocomplete = ({
17
19
  invalid = false,
18
20
  onValueChange,
19
21
  onInputValueChange,
20
- noMatchesMessage = 'No matches found',
22
+ onOpenChange,
23
+ locale: { loading: loadingMessage = 'Loading...', noMatches: noMatchesMessage = 'No matches found' } = {},
21
24
  highlightMatch = true,
22
25
  showTrigger = true,
23
26
  startAdornment,
24
27
  }: DsAutocompleteProps) => {
28
+ const [filterText, setFilterText] = useState('');
25
29
  const filterUtils = useFilter({ sensitivity: 'base' });
26
30
 
27
- const { collection, filter } = useListCollection({
28
- initialItems: options.map((opt) => opt.label),
29
- filter: (item, query) => filterUtils.contains(item, query),
31
+ const filteredOptions = filterText
32
+ ? options.filter((opt) => filterUtils.contains(opt.label, filterText))
33
+ : options;
34
+
35
+ const collection = createListCollection<DsAutocompleteOption>({
36
+ items: filteredOptions,
37
+ itemToString: (item) => item.label,
38
+ itemToValue: (item) => item.value,
30
39
  });
31
40
 
32
41
  const handleInputValueChange = (details: Combobox.InputValueChangeDetails) => {
33
- filter(details.inputValue);
42
+ setFilterText(details.inputValue);
34
43
  onInputValueChange?.(details.inputValue);
35
44
  };
36
45
 
37
- const handleValueChange = (details: Combobox.ValueChangeDetails) => {
38
- const selectedLabel = details.value[0];
39
- if (!selectedLabel) {
40
- return;
41
- }
42
-
43
- const selectedOption = options.find((opt) => opt.label === selectedLabel);
46
+ const handleValueChange = (details: Combobox.ValueChangeDetails<DsAutocompleteOption>) => {
47
+ const selectedItem = details.items[0];
44
48
 
45
- if (selectedOption) {
46
- onValueChange?.(selectedOption.value);
49
+ if (selectedItem) {
50
+ onValueChange?.(selectedItem.value);
47
51
  }
48
52
  };
49
53
 
54
+ const handleOpenChange = (details: Combobox.OpenChangeDetails) => {
55
+ onOpenChange?.(details.open);
56
+ };
57
+
50
58
  const rootClass = classNames(
51
59
  styles.root,
52
60
  {
@@ -66,6 +74,7 @@ export const DsAutocomplete = ({
66
74
  invalid={invalid}
67
75
  onInputValueChange={handleInputValueChange}
68
76
  onValueChange={handleValueChange}
77
+ onOpenChange={handleOpenChange}
69
78
  closeOnSelect
70
79
  >
71
80
  <Combobox.Control className={styles.control}>
@@ -95,31 +104,29 @@ export const DsAutocomplete = ({
95
104
  <Portal>
96
105
  <Combobox.Positioner className={styles.positioner}>
97
106
  <Combobox.Content className={styles.content}>
98
- {collection.items.length === 0 ? (
107
+ {loading && <div className={styles.loading}>{loadingMessage}</div>}
108
+
109
+ {!loading && collection.items.length === 0 && (
99
110
  <div className={styles.noMatches}>{noMatchesMessage}</div>
100
- ) : (
111
+ )}
112
+
113
+ {!loading && collection.items.length > 0 && (
101
114
  <Combobox.ItemGroup className={styles.itemGroup}>
102
- <Combobox.Context>
103
- {(context) =>
104
- collection.items.map((item) => {
105
- const option = options.find((opt) => opt.label === item);
106
- return (
107
- <Combobox.Item key={item} item={item} className={styles.item}>
108
- {option?.icon && (
109
- <DsIcon className={styles.itemIcon} icon={option.icon} aria-hidden="true" />
110
- )}
111
- <Combobox.ItemText className={styles.itemText}>
112
- {highlightMatch ? (
113
- <Highlight query={context.inputValue} text={item} ignoreCase />
114
- ) : (
115
- item
116
- )}
117
- </Combobox.ItemText>
118
- </Combobox.Item>
119
- );
120
- })
121
- }
122
- </Combobox.Context>
115
+ {collection.items.map((option) => (
116
+ <Combobox.Item key={option.value} item={option} className={styles.item}>
117
+ {option.icon && (
118
+ <DsIcon className={styles.itemIcon} icon={option.icon} aria-hidden="true" />
119
+ )}
120
+
121
+ <Combobox.ItemText className={styles.itemText}>
122
+ {highlightMatch ? (
123
+ <Highlight query={filterText} text={option.label} ignoreCase />
124
+ ) : (
125
+ option.label
126
+ )}
127
+ </Combobox.ItemText>
128
+ </Combobox.Item>
129
+ ))}
123
130
  </Combobox.ItemGroup>
124
131
  )}
125
132
  </Combobox.Content>
@@ -22,9 +22,16 @@ export interface DsAutocompleteProps {
22
22
  */
23
23
  id?: string;
24
24
  /**
25
- * Options to display in the dropdown
25
+ * Options to display in the dropdown.
26
+ * For async/server-driven search, update this prop with fetched results.
26
27
  */
27
- options: DsAutocompleteOption[];
28
+ options?: DsAutocompleteOption[];
29
+ /**
30
+ * Whether the autocomplete is in a loading state.
31
+ * When true, a loading message is shown in the dropdown.
32
+ */
33
+ loading?: boolean;
34
+
28
35
  /**
29
36
  * Additional styles to apply to the component
30
37
  */
@@ -45,19 +52,22 @@ export interface DsAutocompleteProps {
45
52
  * Whether the autocomplete is in an invalid state
46
53
  */
47
54
  invalid?: boolean;
55
+
48
56
  /**
49
- * Event handler called when the value changes (when an option is selected or custom value is entered)
50
- */
51
- onValueChange?: (value: string) => void;
52
- /**
53
- * Event handler called when the input value changes (on every keystroke)
54
- */
55
- onInputValueChange?: (value: string) => void;
56
- /**
57
- * Message to display when no options match the input
58
- * @default 'No matches found'
59
- */
60
- noMatchesMessage?: string;
57
+ * Locale strings for the autocomplete component
58
+ */
59
+ locale?: {
60
+ /**
61
+ * Message to display while results are loading
62
+ * @default 'Loading...'
63
+ */
64
+ loading?: string;
65
+ /**
66
+ * Message to display when no options match the input
67
+ * @default 'No matches found'
68
+ */
69
+ noMatches?: string;
70
+ };
61
71
  /**
62
72
  * Whether to highlight the matching text in the dropdown options
63
73
  * @default true
@@ -73,4 +83,17 @@ export interface DsAutocompleteProps {
73
83
  * Content to display at the start of the input (e.g., a search icon).
74
84
  */
75
85
  startAdornment?: React.ReactNode;
86
+
87
+ /**
88
+ * Event handler called when the value changes (when an option is selected)
89
+ */
90
+ onValueChange?: (value: string) => void;
91
+ /**
92
+ * Event handler called when the input value changes (on every keystroke)
93
+ */
94
+ onInputValueChange?: (value: string) => void;
95
+ /**
96
+ * Event handler called when the dropdown opens or closes
97
+ */
98
+ onOpenChange?: (open: boolean) => void;
76
99
  }
@@ -154,7 +154,7 @@ $button-size-large: 40px;
154
154
  color: var(--color-background-action);
155
155
 
156
156
  .content {
157
- background: var(--color-background);
157
+ background: transparent;
158
158
  border-color: transparent;
159
159
  }
160
160
 
@@ -238,7 +238,7 @@ $button-size-large: 40px;
238
238
  color: var(--color-background-selected);
239
239
 
240
240
  .content {
241
- background: var(--color-background);
241
+ background: transparent;
242
242
  border-color: transparent;
243
243
  }
244
244
 
@@ -267,10 +267,6 @@ $button-size-large: 40px;
267
267
  &:disabled {
268
268
  cursor: not-allowed;
269
269
  color: var(--color-background-disable);
270
-
271
- .content {
272
- border-color: var(--color-background);
273
- }
274
270
  }
275
271
  }
276
272
 
@@ -361,7 +357,7 @@ $button-size-large: 40px;
361
357
  color: var(--color-background-danger-strong);
362
358
 
363
359
  .content {
364
- background: var(--color-background);
360
+ background: transparent;
365
361
  border-color: transparent;
366
362
  }
367
363
 
@@ -0,0 +1,62 @@
1
+ $scrollbar-width: 8px;
2
+ $scrollbar-thumb-width: 3.2px;
3
+ $scrollbar-border-radius-pill: 100px;
4
+ $shadow-height: 34px;
5
+ $shadow-border-radius: 0 0 32px 32px;
6
+ $transition-duration: 0.2s;
7
+
8
+ .thread {
9
+ display: flex;
10
+ flex-direction: column;
11
+ flex: 1;
12
+ position: relative;
13
+ overflow: hidden;
14
+ min-height: 0;
15
+ }
16
+
17
+ .initialComment {
18
+ flex-shrink: 0;
19
+ position: relative;
20
+ }
21
+
22
+ .followingComments {
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: var(--spacing-sm);
26
+ flex: 1;
27
+ overflow-y: auto;
28
+ min-height: 0;
29
+ padding: var(--spacing-sm) 0;
30
+
31
+ &::-webkit-scrollbar {
32
+ width: $scrollbar-width;
33
+ }
34
+
35
+ &::-webkit-scrollbar-track {
36
+ background: transparent;
37
+ }
38
+
39
+ &::-webkit-scrollbar-thumb {
40
+ background: var(--color-background-tertiary);
41
+ border-radius: $scrollbar-border-radius-pill;
42
+ width: $scrollbar-thumb-width;
43
+ }
44
+ }
45
+
46
+ .overflowShadow {
47
+ position: absolute;
48
+ bottom: 0;
49
+ transform: translateY(100%);
50
+ left: 0;
51
+ right: 0;
52
+ height: $shadow-height;
53
+ border-radius: $shadow-border-radius;
54
+ background: linear-gradient(180deg, rgba(0, 26, 71, 0.2) -135.47%, rgba(0, 26, 71, 0) 54.98%);
55
+ pointer-events: none;
56
+ opacity: 0;
57
+ transition: opacity $transition-duration ease;
58
+
59
+ &.visible {
60
+ opacity: 1;
61
+ }
62
+ }
@@ -0,0 +1,83 @@
1
+ import { useRef, useEffect } from 'react';
2
+ import classNames from 'classnames';
3
+ import styles from './ds-comment-thread.module.scss';
4
+ import { DsThreadItem } from '../ds-thread-item';
5
+ import { useScrollOverflow } from '../../hooks';
6
+ import type { DsCommentThreadProps } from './ds-comment-thread.types';
7
+ import type { CommentAuthor } from '../../../ds-comment-card';
8
+
9
+ export const DsCommentThread = ({
10
+ messages,
11
+ commentAuthor,
12
+ currentUser,
13
+ onEditMessage,
14
+ onDeleteMessage,
15
+ onMarkUnread,
16
+ onResolved,
17
+ className,
18
+ }: DsCommentThreadProps) => {
19
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
20
+ const bottomRef = useRef<HTMLDivElement>(null);
21
+
22
+ const initialMessage = messages.find((m) => m.isInitialMessage) || messages[0];
23
+ const followingMessages = messages.filter((m) => m.id !== initialMessage?.id);
24
+
25
+ const hasOverflow = useScrollOverflow(scrollContainerRef, messages.length);
26
+
27
+ useEffect(() => {
28
+ bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
29
+ }, [messages.length]);
30
+
31
+ const isCurrentUserMessage = (author: CommentAuthor): boolean => currentUser?.id === author.id;
32
+ const isCommentAuthorMessage = (author: CommentAuthor): boolean => commentAuthor.id === author.id;
33
+ const canModify = (author: CommentAuthor): boolean => isCurrentUserMessage(author);
34
+
35
+ if (!initialMessage) {
36
+ return null;
37
+ }
38
+
39
+ return (
40
+ <div className={classNames(styles.thread, className)}>
41
+ <div className={styles.initialComment}>
42
+ <DsThreadItem
43
+ id={initialMessage.id}
44
+ author={initialMessage.author}
45
+ content={initialMessage.content}
46
+ createdAt={initialMessage.createdAt}
47
+ isCommentAuthorMessage={isCommentAuthorMessage(initialMessage.author)}
48
+ canModify={canModify(initialMessage.author)}
49
+ onEdit={canModify(initialMessage.author) ? onEditMessage : undefined}
50
+ onDelete={canModify(initialMessage.author) ? onDeleteMessage : undefined}
51
+ onMarkUnread={onMarkUnread}
52
+ onResolved={onResolved}
53
+ />
54
+ <div
55
+ className={classNames(styles.overflowShadow, {
56
+ [styles.visible]: hasOverflow,
57
+ })}
58
+ />
59
+ </div>
60
+
61
+ {followingMessages.length > 0 && (
62
+ <div ref={scrollContainerRef} className={styles.followingComments}>
63
+ {followingMessages.map((message) => (
64
+ <DsThreadItem
65
+ key={message.id}
66
+ id={message.id}
67
+ author={message.author}
68
+ content={message.content}
69
+ createdAt={message.createdAt}
70
+ isCommentAuthorMessage={isCommentAuthorMessage(message.author)}
71
+ canModify={canModify(message.author)}
72
+ onEdit={canModify(message.author) ? onEditMessage : undefined}
73
+ onDelete={canModify(message.author) ? onDeleteMessage : undefined}
74
+ onMarkUnread={onMarkUnread}
75
+ onResolved={onResolved}
76
+ />
77
+ ))}
78
+ <div ref={bottomRef} />
79
+ </div>
80
+ )}
81
+ </div>
82
+ );
83
+ };
@@ -0,0 +1,12 @@
1
+ import type { CommentMessage, CommentAuthor } from '../../../ds-comment-card';
2
+
3
+ export interface DsCommentThreadProps {
4
+ messages: CommentMessage[];
5
+ commentAuthor: CommentAuthor;
6
+ currentUser?: CommentAuthor;
7
+ onEditMessage?: (messageId: string, newContent: string) => void;
8
+ onDeleteMessage?: (messageId: string) => void;
9
+ onMarkUnread?: (messageId: string) => void;
10
+ onResolved?: (messageId: string) => void;
11
+ className?: string;
12
+ }
@@ -0,0 +1,2 @@
1
+ export { DsCommentThread } from './ds-comment-thread';
2
+ export * from './ds-comment-thread.types';
@@ -0,0 +1,149 @@
1
+ $avatar-size: 24px;
2
+ $avatar-fallback-font-size: 10px;
3
+ $header-height: 24px;
4
+ $icon-button-size: 16px;
5
+ $dropdown-min-width: 180px;
6
+ $border-radius-circle: 50%;
7
+ $transition-duration: 0.2s;
8
+
9
+ @mixin icon-button-size {
10
+ &#{&} {
11
+ width: $icon-button-size;
12
+ height: $icon-button-size;
13
+ min-width: $icon-button-size;
14
+ min-height: $icon-button-size;
15
+ padding: 0;
16
+ border: none;
17
+ }
18
+ }
19
+
20
+ .iconButtonContent {
21
+ height: $icon-button-size !important;
22
+ min-height: $icon-button-size !important;
23
+ padding: 0 !important;
24
+ border: none !important;
25
+ }
26
+
27
+ .threadItem {
28
+ display: flex;
29
+ gap: var(--spacing-lg);
30
+ padding: var(--spacing-sm) var(--spacing-standard);
31
+ position: relative;
32
+
33
+ &:hover {
34
+ .actions {
35
+ opacity: 1;
36
+ }
37
+ }
38
+
39
+ &.alignRight {
40
+ flex-direction: row-reverse;
41
+
42
+ .content {
43
+ align-items: flex-end;
44
+ }
45
+
46
+ .message {
47
+ text-align: right;
48
+ }
49
+ }
50
+ }
51
+
52
+ .avatarWrapper {
53
+ flex-shrink: 0;
54
+ }
55
+
56
+ .avatar {
57
+ width: $avatar-size;
58
+ height: $avatar-size;
59
+ border-radius: $border-radius-circle;
60
+ object-fit: cover;
61
+ }
62
+
63
+ .avatarFallback {
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ width: $avatar-size;
68
+ height: $avatar-size;
69
+ border-radius: $border-radius-circle;
70
+ background: var(--color-background-tertiary);
71
+ font-size: $avatar-fallback-font-size;
72
+ font-weight: var(--font-weight-semi-bold);
73
+ color: var(--color-font-main);
74
+ text-transform: uppercase;
75
+ }
76
+
77
+ .content {
78
+ display: flex;
79
+ flex-direction: column;
80
+ gap: var(--spacing-3xs);
81
+ flex: 1;
82
+ min-width: 0;
83
+ }
84
+
85
+ .header {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: var(--spacing-xs);
89
+ height: $header-height;
90
+ }
91
+
92
+ .authorName {
93
+ font-size: var(--font-size-md);
94
+ font-weight: var(--font-weight-semi-bold);
95
+ line-height: var(--line-height-md);
96
+ color: var(--color-font-main);
97
+ }
98
+
99
+ .timestamp {
100
+ font-size: var(--font-size-md);
101
+ font-weight: var(--font-weight-regular);
102
+ line-height: var(--line-height-md);
103
+ color: var(--color-font-secondary);
104
+ }
105
+
106
+ .message {
107
+ font-size: var(--font-size-sm);
108
+ font-weight: var(--font-weight-regular);
109
+ line-height: var(--line-height-sm);
110
+ color: var(--color-font-main);
111
+ word-wrap: break-word;
112
+ white-space: pre-wrap;
113
+ }
114
+
115
+ .actions {
116
+ display: flex;
117
+ align-items: flex-start;
118
+ gap: var(--spacing-xs);
119
+ opacity: 0;
120
+ transition: opacity $transition-duration ease;
121
+ }
122
+
123
+ .actionButton {
124
+ @include icon-button-size;
125
+ }
126
+
127
+ .editContainer {
128
+ width: 100%;
129
+ align-self: stretch;
130
+ }
131
+
132
+ .editTextarea {
133
+ width: 100%;
134
+ }
135
+
136
+ .editActions {
137
+ display: flex;
138
+ gap: var(--spacing-xs);
139
+ justify-content: flex-end;
140
+ margin-top: var(--spacing-xs);
141
+ }
142
+
143
+ .dropdownContent {
144
+ min-width: $dropdown-min-width;
145
+
146
+ &#{&} {
147
+ z-index: 1300;
148
+ }
149
+ }