@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,19 @@
1
+ .running {
2
+ color: var(--color-primary-700-default);
3
+ }
4
+
5
+ .warning {
6
+ color: var(--color-warning-400);
7
+ }
8
+
9
+ .failed {
10
+ color: var(--color-negative-400-default);
11
+ }
12
+
13
+ .paused {
14
+ color: var(--color-purple-600);
15
+ }
16
+
17
+ .inactive {
18
+ color: var(--color-font-disabled);
19
+ }
@@ -0,0 +1,35 @@
1
+ import classNames from 'classnames';
2
+ import styles from './ds-filter-status-icon.module.scss';
3
+ import type { DsFilterStatusIconProps } from './ds-filter-status-icon.types';
4
+ import { DsIcon } from '../ds-icon';
5
+
6
+ const iconMap = {
7
+ running: 'special-running',
8
+ warning: 'special-warning',
9
+ failed: 'special-failed',
10
+ paused: 'special-paused',
11
+ } as const;
12
+
13
+ /**
14
+ * Design system Filter Status Icon component
15
+ * Status icons for toggle filter buttons to help users quickly distinguish states
16
+ */
17
+ export const DsFilterStatusIcon = ({
18
+ status,
19
+ active = true,
20
+ size = 'small',
21
+ className,
22
+ style,
23
+ }: DsFilterStatusIconProps) => {
24
+ const statusLabel = active ? `${status} status` : `${status} status (inactive)`;
25
+
26
+ return (
27
+ <DsIcon
28
+ className={classNames(active ? styles[status] : styles.inactive, className)}
29
+ style={style}
30
+ icon={iconMap[status]}
31
+ size={size}
32
+ aria-label={statusLabel}
33
+ />
34
+ );
35
+ };
@@ -0,0 +1,31 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { IconSize } from '../ds-icon';
3
+
4
+ export const filterStatuses = ['running', 'warning', 'failed', 'paused'] as const;
5
+
6
+ export type FilterStatus = (typeof filterStatuses)[number];
7
+
8
+ export interface DsFilterStatusIconProps {
9
+ /**
10
+ * The filter status type
11
+ */
12
+ status: FilterStatus;
13
+ /**
14
+ * Whether the status icon is active or non-active
15
+ * @default true
16
+ */
17
+ active?: boolean;
18
+ /**
19
+ * Icon size
20
+ * @default 'small'
21
+ */
22
+ size?: IconSize;
23
+ /**
24
+ * Additional CSS class names
25
+ */
26
+ className?: string;
27
+ /**
28
+ * Additional styles to apply to the component
29
+ */
30
+ style?: CSSProperties;
31
+ }
@@ -0,0 +1,3 @@
1
+ export { DsFilterStatusIcon } from './ds-filter-status-icon';
2
+ export type { DsFilterStatusIconProps, FilterStatus } from './ds-filter-status-icon.types';
3
+ export { filterStatuses } from './ds-filter-status-icon.types';
@@ -62,6 +62,10 @@ import { SpecialNetgenS } from './special-netgen-s';
62
62
  // Active state icons
63
63
  import { SpecialFilterListActive } from './special-filter-list-active';
64
64
  import { SpecialCachedActive } from './special-cached-active';
65
+ import { SpecialRunning } from './special-running';
66
+ import { SpecialWarning } from './special-warning';
67
+ import { SpecialFailed } from './special-failed';
68
+ import { SpecialPaused } from './special-paused';
65
69
 
66
70
  export type CustomIconComponent = FC<SVGProps<SVGSVGElement>>;
67
71
 
@@ -128,6 +132,12 @@ export const customIcons = {
128
132
  // Active state
129
133
  'special-filter-list-active': SpecialFilterListActive,
130
134
  'special-cached-active': SpecialCachedActive,
135
+
136
+ // Filter status
137
+ 'special-running': SpecialRunning,
138
+ 'special-warning': SpecialWarning,
139
+ 'special-failed': SpecialFailed,
140
+ 'special-paused': SpecialPaused,
131
141
  } as const satisfies Record<string, CustomIconComponent>;
132
142
 
133
143
  export type CustomIconName = keyof typeof customIcons;
@@ -0,0 +1,16 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ /**
4
+ * Special Failed icon
5
+ * @figma https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=29013-18104&t=QK7IwYU1ovLUDr9r-0
6
+ */
7
+ export const SpecialFailed: FC<SVGProps<SVGSVGElement>> = (props) => {
8
+ return (
9
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path
11
+ d="M5.4001 8.60001H10.6001C10.7701 8.60001 10.9126 8.54284 11.0276 8.42851C11.1426 8.31417 11.2001 8.17251 11.2001 8.00351C11.2001 7.83451 11.1426 7.69167 11.0276 7.57501C10.9126 7.45834 10.7701 7.40001 10.6001 7.40001H5.4001C5.2301 7.40001 5.0876 7.45717 4.9726 7.57151C4.8576 7.68584 4.8001 7.82751 4.8001 7.99651C4.8001 8.16551 4.8576 8.30834 4.9726 8.42501C5.0876 8.54167 5.2301 8.60001 5.4001 8.60001ZM8.00476 14.4C7.12388 14.4 6.29454 14.2333 5.51676 13.9C4.73899 13.5667 4.05843 13.1083 3.4751 12.525C2.89176 11.9417 2.43343 11.2613 2.1001 10.484C1.76676 9.70667 1.6001 8.87612 1.6001 7.99234C1.6001 7.10856 1.76676 6.28056 2.1001 5.50834C2.43343 4.73612 2.89176 4.05834 3.4751 3.47501C4.05843 2.89167 4.73876 2.43334 5.5161 2.10001C6.29343 1.76667 7.12399 1.60001 8.00776 1.60001C8.89154 1.60001 9.71954 1.76667 10.4918 2.10001C11.264 2.43334 11.9418 2.89167 12.5251 3.47501C13.1084 4.05834 13.5668 4.73734 13.9001 5.51201C14.2334 6.28678 14.4001 7.11456 14.4001 7.99534C14.4001 8.87623 14.2334 9.70556 13.9001 10.4833C13.5668 11.2611 13.1084 11.9417 12.5251 12.525C11.9418 13.1083 11.2628 13.5667 10.4881 13.9C9.71332 14.2333 8.88554 14.4 8.00476 14.4Z"
12
+ fill="currentColor"
13
+ />
14
+ </svg>
15
+ );
16
+ };
@@ -0,0 +1,16 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ /**
4
+ * Special Paused icon
5
+ * @figma https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=29013-18104&t=QK7IwYU1ovLUDr9r-0
6
+ */
7
+ export const SpecialPaused: FC<SVGProps<SVGSVGElement>> = (props) => {
8
+ return (
9
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path
11
+ d="M6.5966 10.4C6.7656 10.4 6.90843 10.3426 7.0251 10.2277C7.14176 10.1128 7.2001 9.97045 7.2001 9.80067V6.20467C7.2001 6.0349 7.14293 5.89167 7.0286 5.77501C6.91426 5.65834 6.7726 5.60001 6.6036 5.60001C6.4346 5.60001 6.29176 5.65745 6.1751 5.77234C6.05843 5.88723 6.0001 6.02956 6.0001 6.19934V9.79534C6.0001 9.96512 6.05726 10.1083 6.1716 10.225C6.28593 10.3417 6.4276 10.4 6.5966 10.4ZM9.3966 10.4C9.5656 10.4 9.70843 10.3426 9.8251 10.2277C9.94176 10.1128 10.0001 9.97045 10.0001 9.80067V6.20467C10.0001 6.0349 9.94293 5.89167 9.8286 5.77501C9.71426 5.65834 9.5726 5.60001 9.4036 5.60001C9.2346 5.60001 9.09176 5.65745 8.9751 5.77234C8.85843 5.88723 8.8001 6.02956 8.8001 6.19934V9.79534C8.8001 9.96512 8.85726 10.1083 8.9716 10.225C9.08593 10.3417 9.2276 10.4 9.3966 10.4ZM8.00476 14.4C7.12388 14.4 6.29454 14.2333 5.51676 13.9C4.73899 13.5667 4.05843 13.1083 3.4751 12.525C2.89176 11.9417 2.43343 11.2613 2.1001 10.484C1.76676 9.70667 1.6001 8.87612 1.6001 7.99234C1.6001 7.10856 1.76676 6.28056 2.1001 5.50834C2.43343 4.73612 2.89176 4.05834 3.4751 3.47501C4.05843 2.89167 4.73876 2.43334 5.5161 2.10001C6.29343 1.76667 7.12399 1.60001 8.00776 1.60001C8.89154 1.60001 9.71954 1.76667 10.4918 2.10001C11.264 2.43334 11.9418 2.89167 12.5251 3.47501C13.1084 4.05834 13.5668 4.73734 13.9001 5.51201C14.2334 6.28678 14.4001 7.11456 14.4001 7.99534C14.4001 8.87623 14.2334 9.70556 13.9001 10.4833C13.5668 11.2611 13.1084 11.9417 12.5251 12.525C11.9418 13.1083 11.2628 13.5667 10.4881 13.9C9.71332 14.2333 8.88554 14.4 8.00476 14.4Z"
12
+ fill="currentColor"
13
+ />
14
+ </svg>
15
+ );
16
+ };
@@ -0,0 +1,16 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ /**
4
+ * Special Running icon
5
+ * @figma https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=27983-34077
6
+ */
7
+ export const SpecialRunning: FC<SVGProps<SVGSVGElement>> = (props) => {
8
+ return (
9
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path
11
+ d="M7.08343 10.3667L10.2001 8.38334C10.3445 8.29067 10.4168 8.16195 10.4168 7.99717C10.4168 7.83239 10.3445 7.70556 10.2001 7.61667L7.08343 5.63334C6.92788 5.54445 6.7751 5.54184 6.6251 5.62551C6.4751 5.70917 6.4001 5.83956 6.4001 6.01667V9.98334C6.4001 10.1605 6.4751 10.2908 6.6251 10.3745C6.7751 10.4582 6.92788 10.4556 7.08343 10.3667ZM8.00476 14.4C7.12388 14.4 6.29454 14.2333 5.51676 13.9C4.73899 13.5667 4.05843 13.1083 3.4751 12.525C2.89176 11.9417 2.43343 11.2613 2.1001 10.484C1.76676 9.70667 1.6001 8.87612 1.6001 7.99234C1.6001 7.10856 1.76676 6.28056 2.1001 5.50834C2.43343 4.73612 2.89176 4.05834 3.4751 3.47501C4.05843 2.89167 4.73876 2.43334 5.5161 2.10001C6.29343 1.76667 7.12399 1.60001 8.00776 1.60001C8.89154 1.60001 9.71954 1.76667 10.4918 2.10001C11.264 2.43334 11.9418 2.89167 12.5251 3.47501C13.1084 4.05834 13.5668 4.73734 13.9001 5.51201C14.2334 6.28678 14.4001 7.11456 14.4001 7.99534C14.4001 8.87623 14.2334 9.70556 13.9001 10.4833C13.5668 11.2611 13.1084 11.9417 12.5251 12.525C11.9418 13.1083 11.2628 13.5667 10.4881 13.9C9.71332 14.2333 8.88554 14.4 8.00476 14.4Z"
12
+ fill="currentColor"
13
+ />
14
+ </svg>
15
+ );
16
+ };
@@ -0,0 +1,16 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ /**
4
+ * Special Warning icon
5
+ * @figma https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=28702-63149
6
+ */
7
+ export const SpecialWarning: FC<SVGProps<SVGSVGElement>> = (props) => {
8
+ return (
9
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path
11
+ d="M1.8654 13.6C1.74418 13.6 1.6379 13.5725 1.54657 13.5175C1.45535 13.4625 1.38435 13.39 1.33357 13.3C1.27801 13.2111 1.25301 13.1111 1.25857 13C1.26413 12.8889 1.29468 12.7833 1.35024 12.6833L7.48357 2.44999C7.53913 2.34999 7.61429 2.27777 7.70907 2.23332C7.80374 2.18888 7.90096 2.16666 8.00074 2.16666C8.1004 2.16666 8.19746 2.18888 8.2919 2.23332C8.38635 2.27777 8.46135 2.34999 8.5169 2.44999L14.6502 12.6833C14.7058 12.7833 14.7363 12.8889 14.7419 13C14.7475 13.1111 14.7225 13.2111 14.6669 13.3C14.6113 13.3889 14.5391 13.4611 14.4502 13.5167C14.3613 13.5722 14.2563 13.6 14.1351 13.6H1.8654ZM7.99674 11.6C8.16574 11.6 8.30857 11.5428 8.42524 11.4285C8.5419 11.3142 8.60024 11.1725 8.60024 11.0035C8.60024 10.8345 8.54307 10.6917 8.42874 10.575C8.3144 10.4583 8.17274 10.4 8.00374 10.4C7.83474 10.4 7.6919 10.4572 7.57524 10.5715C7.45857 10.6858 7.40024 10.8275 7.40024 10.9965C7.40024 11.1655 7.4574 11.3083 7.57174 11.425C7.68607 11.5417 7.82774 11.6 7.99674 11.6ZM7.99674 9.59999C8.16574 9.59999 8.30857 9.54249 8.42524 9.42749C8.5419 9.31249 8.60024 9.16999 8.60024 8.99999V6.99999C8.60024 6.82999 8.54307 6.68749 8.42874 6.57249C8.3144 6.45749 8.17274 6.39999 8.00374 6.39999C7.83474 6.39999 7.6919 6.45749 7.57524 6.57249C7.45857 6.68749 7.40024 6.82999 7.40024 6.99999V8.99999C7.40024 9.16999 7.4574 9.31249 7.57174 9.42749C7.68607 9.54249 7.82774 9.59999 7.99674 9.59999Z"
12
+ fill="currentColor"
13
+ />
14
+ </svg>
15
+ );
16
+ };
@@ -6,7 +6,7 @@
6
6
  border-inline-end: 1px solid transparent;
7
7
 
8
8
  &[data-state='open']:hover,
9
- &[data-state='open']:focus-within {
9
+ &[data-state='open']:has(:focus-visible) {
10
10
  border-color: var(--color-border);
11
11
  }
12
12
 
@@ -57,12 +57,12 @@
57
57
  }
58
58
 
59
59
  .root:hover &,
60
- .root:focus-within & {
60
+ .root:has(:focus-visible) & {
61
61
  display: flex;
62
62
  }
63
63
 
64
64
  &:hover,
65
- &:focus {
65
+ &:focus-visible {
66
66
  color: var(--color-icon-primary);
67
67
  border-color: var(--color-border-action-primary);
68
68
  outline: none;
@@ -0,0 +1,57 @@
1
+ $icon-medium-size: 44px;
2
+ $transition-duration: 0.2s;
3
+
4
+ .root {
5
+ position: relative;
6
+ display: inline-flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ }
10
+
11
+ .svg {
12
+ position: absolute;
13
+ inset: 0;
14
+ }
15
+
16
+ .track {
17
+ stroke: var(--color-background-tertiary);
18
+ transition: stroke $transition-duration;
19
+ }
20
+
21
+ .progress {
22
+ transition: stroke-dasharray $transition-duration;
23
+ }
24
+
25
+ .default {
26
+ stroke: var(--color-background-selected);
27
+ }
28
+
29
+ .success {
30
+ stroke: var(--color-background-success-strong);
31
+ }
32
+
33
+ .error {
34
+ stroke: var(--color-background-danger-strong);
35
+ }
36
+
37
+ .center {
38
+ position: relative;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ color: var(--color-font-main);
43
+ }
44
+
45
+ .iconSuccess {
46
+ color: var(--color-icon-success);
47
+ }
48
+
49
+ .iconError {
50
+ color: var(--color-icon-danger);
51
+ }
52
+
53
+ .iconMediumSize.iconMediumSize {
54
+ font-size: $icon-medium-size;
55
+ width: 1em;
56
+ height: 1em;
57
+ }
@@ -0,0 +1,111 @@
1
+ import type React from 'react';
2
+ import classNames from 'classnames';
3
+
4
+ import { DsIcon } from '../ds-icon';
5
+ import { DsTypography } from '../ds-typography';
6
+ import styles from './ds-progress-arc.module.scss';
7
+ import type { DsProgressArcProps, ProgressArcVariant } from './ds-progress-arc.types';
8
+ import { clampValue, getArcGeometry, getProgressDasharray, getTrackDasharray } from './utils';
9
+
10
+ const variantStyleMap: Record<ProgressArcVariant, string> = Object.freeze({
11
+ default: styles.default,
12
+ success: styles.success,
13
+ error: styles.error,
14
+ });
15
+
16
+ const DsProgressArc = ({
17
+ value = 0,
18
+ size = 'medium',
19
+ variant = 'default',
20
+ children,
21
+ className,
22
+ style,
23
+ ref,
24
+ ...props
25
+ }: DsProgressArcProps) => {
26
+ const { containerSize, strokeWidth, radius, circumference, arcLength, center, startRotation } =
27
+ getArcGeometry(size);
28
+
29
+ const effectiveValue = variant === 'success' ? 100 : clampValue(value);
30
+ const trackDasharray = getTrackDasharray(arcLength, circumference);
31
+ const progressDasharray = getProgressDasharray(effectiveValue, arcLength, circumference);
32
+
33
+ const renderIcon = (iconName: 'check' | 'close', colorClass: string) => {
34
+ return (
35
+ <DsIcon
36
+ variant="rounded"
37
+ icon={iconName}
38
+ size="large"
39
+ className={classNames({ [styles.iconMediumSize]: size === 'medium' }, colorClass)}
40
+ />
41
+ );
42
+ };
43
+
44
+ const renderCenter = () => {
45
+ if (children) {
46
+ return children;
47
+ }
48
+
49
+ if (variant === 'success') {
50
+ return renderIcon('check', styles.iconSuccess);
51
+ }
52
+
53
+ if (variant === 'error') {
54
+ return renderIcon('close', styles.iconError);
55
+ }
56
+
57
+ return (
58
+ <DsTypography variant={size === 'medium' ? 'heading3' : 'body-md-md'} asChild>
59
+ <span>{effectiveValue}%</span>
60
+ </DsTypography>
61
+ );
62
+ };
63
+
64
+ return (
65
+ <div
66
+ {...props}
67
+ className={classNames(styles.root, className)}
68
+ style={{ width: containerSize, height: containerSize, ...style }}
69
+ ref={ref as React.Ref<HTMLDivElement>}
70
+ role="progressbar"
71
+ aria-valuenow={effectiveValue}
72
+ aria-valuemin={0}
73
+ aria-valuemax={100}
74
+ >
75
+ <svg
76
+ className={styles.svg}
77
+ width={containerSize}
78
+ height={containerSize}
79
+ viewBox={`0 0 ${String(containerSize)} ${String(containerSize)}`}
80
+ >
81
+ <circle
82
+ className={styles.track}
83
+ cx={center}
84
+ cy={center}
85
+ r={radius}
86
+ fill="none"
87
+ strokeWidth={strokeWidth}
88
+ strokeDasharray={trackDasharray}
89
+ strokeLinecap="round"
90
+ transform={`rotate(${String(startRotation)} ${String(center)} ${String(center)})`}
91
+ />
92
+
93
+ <circle
94
+ className={classNames(styles.progress, variantStyleMap[variant])}
95
+ cx={center}
96
+ cy={center}
97
+ r={radius}
98
+ fill="none"
99
+ strokeWidth={strokeWidth}
100
+ strokeDasharray={progressDasharray}
101
+ strokeLinecap="round"
102
+ transform={`rotate(${String(startRotation)} ${String(center)} ${String(center)})`}
103
+ />
104
+ </svg>
105
+
106
+ <div className={styles.center}>{renderCenter()}</div>
107
+ </div>
108
+ );
109
+ };
110
+
111
+ export default DsProgressArc;
@@ -0,0 +1,34 @@
1
+ import type { ComponentPropsWithoutRef, ReactNode, Ref } from 'react';
2
+
3
+ export const progressArcSizes = ['small', 'medium'] as const;
4
+ export type ProgressArcSize = (typeof progressArcSizes)[number];
5
+
6
+ export const progressArcVariants = ['default', 'success', 'error'] as const;
7
+ export type ProgressArcVariant = (typeof progressArcVariants)[number];
8
+
9
+ export interface DsProgressArcProps extends ComponentPropsWithoutRef<'div'> {
10
+ /**
11
+ * Progress value between 0 and 100
12
+ * @default 0
13
+ */
14
+ value?: number;
15
+
16
+ /**
17
+ * Size of the arc progress indicator
18
+ * @default 'medium'
19
+ */
20
+ size?: ProgressArcSize;
21
+
22
+ /**
23
+ * Visual variant of the arc progress indicator
24
+ * @default 'default'
25
+ */
26
+ variant?: ProgressArcVariant;
27
+
28
+ /**
29
+ * Custom content to display in the center of the arc, overriding the default text or icon
30
+ */
31
+ children?: ReactNode;
32
+
33
+ ref?: Ref<HTMLElement>;
34
+ }
@@ -0,0 +1,3 @@
1
+ export { default as DsProgressArc } from './ds-progress-arc';
2
+
3
+ export * from './ds-progress-arc.types';
@@ -0,0 +1,79 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ARC_RATIO, getArcGeometry, getProgressDasharray, getTrackDasharray } from './utils';
3
+
4
+ describe('Progress Arc Utils', () => {
5
+ describe('getArcGeometry', () => {
6
+ it('should return correct geometry for small size', () => {
7
+ const geo = getArcGeometry('small');
8
+
9
+ expect(geo.containerSize).toBe(80);
10
+ expect(geo.strokeWidth).toBe(4);
11
+ expect(geo.radius).toBe(38);
12
+ expect(geo.center).toBe(40);
13
+ expect(geo.startRotation).toBe(135);
14
+ expect(geo.circumference).toBeCloseTo(2 * Math.PI * 38);
15
+ expect(geo.arcLength).toBeCloseTo(ARC_RATIO * geo.circumference);
16
+ });
17
+
18
+ it('should return correct geometry for medium size', () => {
19
+ const geo = getArcGeometry('medium');
20
+
21
+ expect(geo.containerSize).toBe(120);
22
+ expect(geo.strokeWidth).toBe(6);
23
+ expect(geo.radius).toBe(57);
24
+ expect(geo.center).toBe(60);
25
+ expect(geo.startRotation).toBe(135);
26
+ expect(geo.circumference).toBeCloseTo(2 * Math.PI * 57);
27
+ expect(geo.arcLength).toBeCloseTo(ARC_RATIO * geo.circumference);
28
+ });
29
+
30
+ it('should have arcLength as 3/4 of circumference', () => {
31
+ const geo = getArcGeometry('medium');
32
+
33
+ expect(geo.arcLength / geo.circumference).toBeCloseTo(ARC_RATIO);
34
+ });
35
+ });
36
+
37
+ describe('getTrackDasharray', () => {
38
+ it('should format arcLength and circumference as space-separated string', () => {
39
+ const result = getTrackDasharray(100, 200);
40
+
41
+ expect(result).toBe('100 200');
42
+ });
43
+
44
+ it('should handle decimal values', () => {
45
+ const geo = getArcGeometry('small');
46
+ const result = getTrackDasharray(geo.arcLength, geo.circumference);
47
+
48
+ expect(result).toBe(`${String(geo.arcLength)} ${String(geo.circumference)}`);
49
+ });
50
+ });
51
+
52
+ describe('getProgressDasharray', () => {
53
+ it('should return zero progress length for value 0', () => {
54
+ const result = getProgressDasharray(0, 100, 200);
55
+
56
+ expect(result).toBe('0 200');
57
+ });
58
+
59
+ it('should return full arc length for value 100', () => {
60
+ const result = getProgressDasharray(100, 100, 200);
61
+
62
+ expect(result).toBe('100 200');
63
+ });
64
+
65
+ it('should return half arc length for value 50', () => {
66
+ const result = getProgressDasharray(50, 100, 200);
67
+
68
+ expect(result).toBe('50 200');
69
+ });
70
+
71
+ it('should calculate correctly with real geometry values', () => {
72
+ const geo = getArcGeometry('medium');
73
+ const result = getProgressDasharray(75, geo.arcLength, geo.circumference);
74
+ const expectedProgress = (75 / 100) * geo.arcLength;
75
+
76
+ expect(result).toBe(`${String(expectedProgress)} ${String(geo.circumference)}`);
77
+ });
78
+ });
79
+ });
@@ -0,0 +1,61 @@
1
+ import type { ProgressArcSize } from './ds-progress-arc.types';
2
+
3
+ export { clampValue } from '../../utils/numbers';
4
+
5
+ const ARC_DEGREES = 270;
6
+ const FULL_CIRCLE = 360;
7
+ const START_ROTATION = 135;
8
+
9
+ export const ARC_RATIO = ARC_DEGREES / FULL_CIRCLE;
10
+
11
+ const containerSizeMap: Record<ProgressArcSize, number> = Object.freeze({
12
+ small: 80,
13
+ medium: 120,
14
+ });
15
+
16
+ const strokeWidthMap: Record<ProgressArcSize, number> = Object.freeze({
17
+ small: 4,
18
+ medium: 6,
19
+ });
20
+
21
+ export interface ArcGeometry {
22
+ containerSize: number;
23
+ strokeWidth: number;
24
+ radius: number;
25
+ circumference: number;
26
+ arcLength: number;
27
+ center: number;
28
+ startRotation: number;
29
+ }
30
+
31
+ export const getArcGeometry = (size: ProgressArcSize): ArcGeometry => {
32
+ const containerSize = containerSizeMap[size];
33
+ const strokeWidth = strokeWidthMap[size];
34
+ const radius = (containerSize - strokeWidth) / 2;
35
+ const circumference = 2 * Math.PI * radius;
36
+ const arcLength = ARC_RATIO * circumference;
37
+ const center = containerSize / 2;
38
+
39
+ return {
40
+ containerSize,
41
+ strokeWidth,
42
+ radius,
43
+ circumference,
44
+ arcLength,
45
+ center,
46
+ startRotation: START_ROTATION,
47
+ };
48
+ };
49
+
50
+ export const getTrackDasharray = (arcLength: number, circumference: number): string =>
51
+ `${String(arcLength)} ${String(circumference)}`;
52
+
53
+ export const getProgressDasharray = (
54
+ effectiveValue: number,
55
+ arcLength: number,
56
+ circumference: number,
57
+ ): string => {
58
+ const progressLength = (effectiveValue / 100) * arcLength;
59
+
60
+ return `${String(progressLength)} ${String(circumference)}`;
61
+ };
@@ -0,0 +1,67 @@
1
+ $transition-duration: 0.2s;
2
+
3
+ .root {
4
+ display: inline-grid;
5
+ }
6
+
7
+ .small {
8
+ --size: 80px;
9
+ --thickness: 4px;
10
+ }
11
+
12
+ .medium {
13
+ --size: 120px;
14
+ --thickness: 6px;
15
+ }
16
+
17
+ .circle {
18
+ grid-area: 1 / 1;
19
+ display: block;
20
+ }
21
+
22
+ .track {
23
+ stroke: var(--color-background-tertiary);
24
+ transition: stroke $transition-duration;
25
+ }
26
+
27
+ .range {
28
+ transition: stroke-dashoffset $transition-duration;
29
+ stroke-linecap: round;
30
+ }
31
+
32
+ .default .range {
33
+ stroke: var(--color-background-selected);
34
+ }
35
+
36
+ .success .track,
37
+ .success .range {
38
+ stroke: var(--color-background-success-strong);
39
+ }
40
+
41
+ .error .range {
42
+ stroke: var(--color-background-danger-strong);
43
+ }
44
+
45
+ .center {
46
+ grid-area: 1 / 1;
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ color: var(--color-font-main);
51
+ }
52
+
53
+ .iconSuccess {
54
+ color: var(--color-icon-success);
55
+ }
56
+
57
+ .iconError {
58
+ color: var(--color-icon-danger);
59
+ }
60
+
61
+ $icon-medium-size: 44px;
62
+
63
+ .iconMediumSize.iconMediumSize {
64
+ font-size: $icon-medium-size;
65
+ width: 1em;
66
+ height: 1em;
67
+ }