@drivenets/design-system 0.15.0 → 0.16.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 (172) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.cjs +868 -200
  3. package/dist/index.d.cts +810 -1181
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +810 -1181
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +852 -196
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +15 -13
  11. package/src/components/ds-alert-banner/ds-alert-banner.tsx +11 -2
  12. package/src/components/ds-autocomplete/ds-autocomplete.figma.ts +36 -0
  13. package/src/components/ds-autocomplete/ds-autocomplete.tsx +2 -0
  14. package/src/components/ds-avatar/ds-avatar.figma.ts +30 -0
  15. package/src/components/ds-avatar/ds-avatar.tsx +6 -1
  16. package/src/components/ds-avatar/ds-avatar.types.ts +5 -2
  17. package/src/components/ds-avatar-group/ds-avatar-group.figma.ts +27 -0
  18. package/src/components/ds-avatar-group/ds-avatar-group.tsx +6 -1
  19. package/src/components/ds-avatar-group/ds-avatar-group.types.ts +3 -1
  20. package/src/components/ds-breadcrumb/ds-breadcrumb.figma.ts +19 -0
  21. package/src/components/ds-breadcrumb/ds-breadcrumb.tsx +2 -0
  22. package/src/components/ds-button/ds-button.tsx +2 -0
  23. package/src/components/ds-button-v3/ds-button-v3.figma.ts +1 -1
  24. package/src/components/ds-button-v3/ds-button-v3.tsx +6 -0
  25. package/src/components/ds-button-v3/index.ts +2 -0
  26. package/src/components/ds-card/ds-card.tsx +7 -3
  27. package/src/components/ds-catalog-layout/ds-catalog-layout.tsx +8 -0
  28. package/src/components/ds-checkbox/ds-checkbox-level-2.figma.ts +40 -0
  29. package/src/components/ds-checkbox/ds-checkbox.figma.ts +45 -0
  30. package/src/components/ds-checkbox/ds-checkbox.module.scss +1 -1
  31. package/src/components/ds-checkbox/ds-checkbox.tsx +9 -1
  32. package/src/components/ds-checkbox-group/ds-checkbox-group.figma.ts +15 -0
  33. package/src/components/ds-checkbox-group/ds-checkbox-group.tsx +2 -0
  34. package/src/components/ds-chip/ds-chip.tsx +2 -0
  35. package/src/components/ds-chip-group/ds-chip-group.tsx +2 -0
  36. package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.tsx +1 -0
  37. package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.tsx +1 -0
  38. package/src/components/ds-comment-bubble/ds-comment-bubble.tsx +1 -0
  39. package/src/components/ds-comment-card/ds-comment-card.tsx +1 -0
  40. package/src/components/ds-comment-indicator/ds-comment-indicator.tsx +1 -0
  41. package/src/components/ds-comments-drawer/ds-comments-drawer.tsx +1 -0
  42. package/src/components/ds-confirmation/ds-confirmation.tsx +1 -0
  43. package/src/components/ds-date-input/ds-date-input.tsx +2 -0
  44. package/src/components/ds-date-picker/ds-date-picker.tsx +9 -0
  45. package/src/components/ds-date-picker/ds-date-picker.types.ts +1 -1
  46. package/src/components/ds-date-range-picker/ds-date-range-picker.tsx +2 -0
  47. package/src/components/ds-dialog/ds-dialog.tsx +9 -0
  48. package/src/components/ds-dialog/ds-dialog.types.ts +1 -1
  49. package/src/components/ds-divider/ds-divider.figma.ts +19 -0
  50. package/src/components/ds-divider/ds-divider.tsx +2 -0
  51. package/src/components/ds-drawer/ds-drawer.tsx +10 -0
  52. package/src/components/ds-drawer/figma/ds-drawer-content.figma.ts +15 -0
  53. package/src/components/ds-drawer/figma/ds-drawer-footer.figma.ts +19 -0
  54. package/src/components/ds-drawer/figma/ds-drawer-header.figma.ts +16 -0
  55. package/src/components/ds-drawer/figma/ds-drawer.figma.ts +36 -0
  56. package/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss +12 -3
  57. package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +24 -6
  58. package/src/components/ds-dropdown-menu/ds-dropdown-menu.types.ts +10 -1
  59. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu-footer.figma.ts +11 -0
  60. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu-group.figma.ts +23 -0
  61. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu-item.figma.ts +29 -0
  62. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu-separator.figma.ts +11 -0
  63. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu.figma.ts +24 -0
  64. package/src/components/ds-expandable-text-input/ds-expandable-text-input.figma.ts +24 -0
  65. package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +1 -0
  66. package/src/components/ds-file-upload/ds-file-upload.figma.ts +29 -0
  67. package/src/components/ds-file-upload/ds-file-upload.tsx +2 -0
  68. package/src/components/ds-filter-status-icon/ds-filter-status-icon.figma.ts +22 -0
  69. package/src/components/ds-filter-status-icon/ds-filter-status-icon.tsx +1 -0
  70. package/src/components/ds-form-control/ds-form-control.tsx +3 -0
  71. package/src/components/ds-grid/index.ts +3 -0
  72. package/src/components/ds-icon/ds-icon.tsx +2 -0
  73. package/src/components/ds-key-value-pair/ds-key-value-pair.figma.ts +24 -0
  74. package/src/components/ds-key-value-pair/ds-key-value-pair.tsx +2 -0
  75. package/src/components/ds-loader/ds-loader.figma.ts +11 -0
  76. package/src/components/ds-loader/ds-loader.tsx +2 -0
  77. package/src/components/ds-modal/ds-modal.tsx +9 -0
  78. package/src/components/ds-number-input/ds-number-input.tsx +2 -0
  79. package/src/components/ds-panel/ds-panel.figma.ts +16 -0
  80. package/src/components/ds-panel/index.ts +2 -0
  81. package/src/components/ds-password-input/ds-password-input.tsx +2 -0
  82. package/src/components/ds-popover/ds-popover.tsx +9 -0
  83. package/src/components/ds-popover/figma/ds-popover-content-item.figma.ts +18 -0
  84. package/src/components/ds-popover/figma/ds-popover-content.figma.ts +15 -0
  85. package/src/components/ds-popover/figma/ds-popover-footer.figma.ts +19 -0
  86. package/src/components/ds-popover/figma/ds-popover-header.figma.ts +16 -0
  87. package/src/components/ds-popover/figma/ds-popover.figma.ts +34 -0
  88. package/src/components/ds-progress-arc/ds-progress-arc.figma.ts +23 -0
  89. package/src/components/ds-progress-arc/ds-progress-arc.tsx +2 -0
  90. package/src/components/ds-progress-donut/ds-progress-donut.figma.ts +45 -0
  91. package/src/components/ds-progress-donut/ds-progress-donut.tsx +2 -0
  92. package/src/components/ds-progress-linear/ds-progress-linear.figma.ts +26 -0
  93. package/src/components/ds-progress-linear/ds-progress-linear.tsx +2 -0
  94. package/src/components/ds-progress-task-bar/ds-progress-task-bar.tsx +2 -0
  95. package/src/components/ds-radio-group/ds-radio-group.tsx +3 -0
  96. package/src/components/ds-radio-group/figma/ds-radio-group-item.figma.ts +24 -0
  97. package/src/components/ds-radio-group/figma/ds-radio-group.figma.ts +14 -0
  98. package/src/components/ds-segment-group/ds-segment-group.tsx +3 -0
  99. package/src/components/ds-segment-group/figma/ds-segment-group-item.figma.ts +25 -0
  100. package/src/components/ds-segment-group/figma/ds-segment-group.figma.ts +23 -0
  101. package/src/components/ds-select/ds-select.tsx +2 -0
  102. package/src/components/ds-site-node/ds-site-node.figma.ts +27 -0
  103. package/src/components/ds-site-node/ds-site-node.module.scss +59 -0
  104. package/src/components/ds-site-node/ds-site-node.tsx +73 -0
  105. package/src/components/ds-site-node/ds-site-node.types.ts +36 -0
  106. package/src/components/ds-site-node/hooks/use-is-overflowing.ts +31 -0
  107. package/src/components/ds-site-node/index.ts +3 -0
  108. package/src/components/ds-skeleton/ds-skeleton-circle.tsx +2 -0
  109. package/src/components/ds-skeleton/ds-skeleton-rect.tsx +2 -0
  110. package/src/components/ds-skeleton/ds-skeleton-text.tsx +2 -0
  111. package/src/components/ds-skeleton/ds-skeleton.ts +7 -1
  112. package/src/components/ds-smart-tabs/ds-smart-tabs.tsx +4 -0
  113. package/src/components/ds-spinner/ds-spinner.figma.ts +19 -0
  114. package/src/components/ds-spinner/ds-spinner.tsx +2 -0
  115. package/src/components/ds-split-button/ds-split-button.tsx +6 -0
  116. package/src/components/ds-status-badge/ds-status-badge.tsx +2 -0
  117. package/src/components/ds-status-badge-v2/index.ts +2 -0
  118. package/src/components/ds-stepper/components/ds-step-content.tsx +2 -0
  119. package/src/components/ds-stepper/components/ds-step-next-button.tsx +2 -0
  120. package/src/components/ds-stepper/components/ds-step.tsx +2 -0
  121. package/src/components/ds-stepper/ds-stepper.figma.ts +38 -0
  122. package/src/components/ds-stepper/ds-stepper.tsx +1 -0
  123. package/src/components/ds-system-status/ds-system-status.tsx +2 -0
  124. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-checkbox.tsx +1 -0
  125. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-date.tsx +1 -0
  126. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-number.tsx +1 -0
  127. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-select.tsx +1 -0
  128. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-text.tsx +1 -0
  129. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.tsx +1 -0
  130. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx +1 -0
  131. package/src/components/ds-table/ds-table.tsx +2 -0
  132. package/src/components/ds-table/index.ts +3 -0
  133. package/src/components/ds-tabs/components/ds-tabs-content/ds-tabs-content.tsx +2 -0
  134. package/src/components/ds-tabs/components/ds-tabs-list/ds-tabs-list.tsx +2 -0
  135. package/src/components/ds-tabs/components/ds-tabs-tab/ds-tabs-tab.tsx +2 -0
  136. package/src/components/ds-tabs/ds-tabs.tsx +2 -0
  137. package/src/components/ds-tag/ds-tag.tsx +2 -0
  138. package/src/components/ds-tag-filter/ds-tag-filter.tsx +2 -0
  139. package/src/components/ds-text-input/ds-input-field-v02.figma.ts +87 -0
  140. package/src/components/ds-text-input/ds-text-input.tsx +2 -0
  141. package/src/components/ds-textarea/ds-textarea.tsx +2 -0
  142. package/src/components/ds-time-picker/ds-time-picker.tsx +2 -0
  143. package/src/components/ds-toast/ds-toast-context.tsx +1 -0
  144. package/src/components/ds-toast/ds-toast.figma.ts +34 -0
  145. package/src/components/ds-toast/ds-toast.tsx +3 -0
  146. package/src/components/ds-toggle/ds-toggle.figma.ts +39 -0
  147. package/src/components/ds-toggle/ds-toggle.tsx +2 -0
  148. package/src/components/ds-tooltip/ds-tooltip.figma.ts +16 -0
  149. package/src/components/ds-tooltip/ds-tooltip.module.scss +8 -1
  150. package/src/components/ds-tooltip/ds-tooltip.tsx +35 -15
  151. package/src/components/ds-tooltip/ds-tooltip.types.ts +40 -0
  152. package/src/components/ds-tree/ds-tree.tsx +16 -0
  153. package/src/components/ds-typography/ds-typography.config.ts +1 -1
  154. package/src/components/ds-typography/ds-typography.figma.ts +63 -0
  155. package/src/components/ds-typography/ds-typography.module.scss +16 -0
  156. package/src/components/ds-typography/ds-typography.tsx +64 -7
  157. package/src/components/ds-typography/ds-typography.types.ts +13 -0
  158. package/src/components/ds-typography/hooks/use-truncation.ts +151 -0
  159. package/src/components/ds-user-card/ds-user-card.figma.ts +16 -0
  160. package/src/components/ds-user-card/ds-user-card.module.scss +25 -0
  161. package/src/components/ds-user-card/ds-user-card.tsx +31 -0
  162. package/src/components/ds-user-card/ds-user-card.types.ts +26 -0
  163. package/src/components/ds-user-card/index.ts +2 -0
  164. package/src/components/ds-vertical-tabs/ds-vertical-tabs.tsx +6 -0
  165. package/src/components/ds-workspace-layout/ds-workspace-layout.tsx +10 -0
  166. package/src/index.ts +2 -0
  167. package/src/styles/_root.scss +468 -363
  168. package/src/styles/shared/_dropdown.scss +2 -2
  169. package/src/styles/styles.scss +0 -3
  170. package/src/styles/_colors.scss +0 -65
  171. package/src/styles/_root_new.scss +0 -451
  172. package/src/styles/_spacing.scss +0 -47
@@ -0,0 +1,63 @@
1
+ // url=https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=39324-248
2
+ // source=https://github.com/drivenets/design-system/tree/main/packages/design-system/src/components/ds-typography
3
+ // component=DsTypography
4
+ import figma from 'figma';
5
+
6
+ const instance = figma.selectedInstance;
7
+
8
+ const variant = instance.getEnum('font-style', {
9
+ heading1: 'heading1',
10
+ heading2: 'heading2',
11
+ heading3: 'heading3',
12
+ heading4: 'heading4',
13
+ 'body-md-reg': 'body-md-reg',
14
+ 'body-md-md': 'body-md-md',
15
+ 'body-md-semi-bold': 'body-md-semi-bold',
16
+ 'body-md-bold': 'body-md-bold',
17
+ 'body-md-link': 'body-md-link',
18
+ 'body-sm-reg': 'body-sm-reg',
19
+ 'body-sm-md': 'body-sm-md',
20
+ 'body-sm-semi-bold': 'body-sm-semi-bold',
21
+ 'body-sm-bold': 'body-sm-bold',
22
+ 'body-sm-link': 'body-sm-link',
23
+ 'body-xs-reg': 'body-xs-reg',
24
+ 'body-xs-md': 'body-xs-md',
25
+ 'body-xs-semi-bold': 'body-xs-semi-bold',
26
+ 'body-xs-bold': 'body-xs-bold',
27
+ 'body-xs-link': 'body-xs-link',
28
+ 'code-sm-reg': 'code-sm-reg',
29
+ 'code-sm-semi-bold': 'code-sm-semi-bold',
30
+ 'code-xs-reg': 'code-xs-reg',
31
+ 'code-xs-semi-bold': 'code-xs-semi-bold',
32
+ });
33
+
34
+ const color = instance.getEnum('color', {
35
+ main: 'main',
36
+ secondary: 'secondary',
37
+ action: 'action',
38
+ 'action-hover': 'action-hover',
39
+ 'action-secondary': 'action-secondary',
40
+ 'action-secondary-hover': 'action-secondary-hover',
41
+ disabled: 'disabled',
42
+ 'light-disabled': 'light-disabled',
43
+ 'on-action': 'on-action',
44
+ 'on-disabled': 'on-disabled',
45
+ placeholder: 'placeholder',
46
+ highlight: 'highlight',
47
+ success: 'success',
48
+ warning: 'warning',
49
+ error: 'error',
50
+ code: 'code',
51
+ });
52
+
53
+ const textNode = instance.children.find((child): child is figma.TextHandle => child.type === 'TEXT');
54
+ const text = textNode?.textContent ?? 'Text';
55
+
56
+ const colorProp = color === 'main' ? '' : figma.code` color="${color}"`;
57
+
58
+ export default {
59
+ example: figma.code`<DsTypography variant="${variant}"${colorProp}>${text}</DsTypography>`,
60
+ imports: ["import { DsTypography } from '@drivenets/design-system'"],
61
+ id: 'ds-typography',
62
+ metadata: { nestable: true },
63
+ };
@@ -1,5 +1,21 @@
1
1
  @use '../../styles/typography';
2
2
 
3
+ .truncateSingle {
4
+ display: block;
5
+ max-width: 100%;
6
+ overflow: hidden;
7
+ white-space: nowrap;
8
+ text-overflow: ellipsis;
9
+ }
10
+
11
+ .truncateMultiline {
12
+ display: -webkit-box;
13
+ -webkit-box-orient: vertical;
14
+ -webkit-line-clamp: var(--ds-typography-line-clamp);
15
+ line-clamp: var(--ds-typography-line-clamp);
16
+ overflow: hidden;
17
+ }
18
+
3
19
  .heading1 {
4
20
  @include typography.heading1;
5
21
  }
@@ -1,6 +1,10 @@
1
+ import { type CSSProperties, useRef } from 'react';
1
2
  import classNames from 'classnames';
2
3
  import type { DsTypographyProps } from './ds-typography.types';
3
4
  import { typographyColors, typographyVariantConfig } from './ds-typography.config';
5
+ import { useTruncation } from './hooks/use-truncation';
6
+ import { mergeRefs } from '../../utils/merge-refs';
7
+ import { DsTooltip } from '../ds-tooltip';
4
8
  import styles from './ds-typography.module.scss';
5
9
 
6
10
  const semanticColorSet = new Set<string>(typographyColors);
@@ -8,6 +12,9 @@ const semanticColorSet = new Set<string>(typographyColors);
8
12
  const resolveColor = (color: string): string =>
9
13
  semanticColorSet.has(color) ? `var(--font-${color})` : color;
10
14
 
15
+ const resolveLineClamp = (truncate: boolean | number): number =>
16
+ typeof truncate === 'number' ? Math.max(1, Math.floor(truncate)) : 1;
17
+
11
18
  /*
12
19
  * Design system Typography component that provides consistent text styling
13
20
  */
@@ -18,25 +25,75 @@ const DsTypography = ({
18
25
  className,
19
26
  style,
20
27
  color,
28
+ truncate = false,
29
+ tooltip = false,
30
+ tooltipContent,
21
31
  children,
22
32
  ...props
23
33
  }: DsTypographyProps) => {
24
34
  const Component = typographyVariantConfig[variant].component;
25
35
 
26
- return (
36
+ const internalRef = useRef<HTMLElement>(null);
37
+
38
+ const truncationEnabled = truncate !== false;
39
+ const lineClamp = resolveLineClamp(truncate);
40
+ const isMultiline = truncationEnabled && lineClamp > 1;
41
+
42
+ const tooltipEnabled = truncationEnabled && tooltip;
43
+ const { isTruncated, getAnchorRect } = useTruncation(internalRef, tooltipEnabled, [children, truncate]);
44
+
45
+ let resolvedStyle: CSSProperties | undefined;
46
+
47
+ if (color || isMultiline || style) {
48
+ resolvedStyle = {};
49
+
50
+ if (color) {
51
+ resolvedStyle.color = resolveColor(color);
52
+ }
53
+
54
+ if (isMultiline) {
55
+ Object.assign(resolvedStyle, { '--ds-typography-line-clamp': lineClamp });
56
+ }
57
+
58
+ if (style) {
59
+ Object.assign(resolvedStyle, style);
60
+ }
61
+ }
62
+
63
+ const truncateClass = truncationEnabled
64
+ ? isMultiline
65
+ ? styles.truncateMultiline
66
+ : styles.truncateSingle
67
+ : undefined;
68
+
69
+ const element = (
27
70
  <Component
28
- // very dynamic ref in here
29
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
30
- // @ts-expect-error
31
- ref={ref}
71
+ ref={mergeRefs(ref, internalRef)}
32
72
  asChild={asChild}
33
- className={classNames(styles[variant], className)}
34
- style={color ? { color: resolveColor(color), ...style } : style}
73
+ className={classNames(styles[variant], truncateClass, className)}
74
+ style={resolvedStyle}
35
75
  {...props}
36
76
  >
37
77
  {children}
38
78
  </Component>
39
79
  );
80
+
81
+ if (!tooltipEnabled) {
82
+ return element;
83
+ }
84
+
85
+ return (
86
+ <DsTooltip
87
+ content={tooltipContent ?? children}
88
+ placement="top"
89
+ disabled={!isTruncated}
90
+ getAnchorRect={getAnchorRect}
91
+ >
92
+ {element}
93
+ </DsTooltip>
94
+ );
40
95
  };
41
96
 
97
+ DsTypography.displayName = 'DsTypography';
98
+
42
99
  export default DsTypography;
@@ -31,6 +31,19 @@ export interface DsTypographyProps {
31
31
  * When true, composes typography styles onto the child element without extra DOM nodes
32
32
  */
33
33
  asChild?: boolean;
34
+ /**
35
+ * Truncates overflowing text with an ellipsis. A number clamps to that many lines
36
+ * (`true` is a single line). Needs a width-constrained layout context.
37
+ */
38
+ truncate?: boolean | number;
39
+ /**
40
+ * Reveals the full text in a tooltip on hover/focus, but only while it overflows.
41
+ */
42
+ tooltip?: boolean;
43
+ /**
44
+ * Tooltip content override; defaults to `children`.
45
+ */
46
+ tooltipContent?: React.ReactNode;
34
47
  /**
35
48
  * The content of the component
36
49
  */
@@ -0,0 +1,151 @@
1
+ import { type RefObject, useCallback, useLayoutEffect, useState } from 'react';
2
+ import type { TooltipAnchorRect } from '../../ds-tooltip';
3
+
4
+ const OVERFLOW_TOLERANCE_PX = 1;
5
+ const FALLBACK_LINE_HEIGHT_RATIO = 1.2;
6
+ const DEFAULT_FONT_SIZE_PX = 16;
7
+ const ELLIPSIS = '\u2026';
8
+
9
+ interface Truncation {
10
+ isTruncated: boolean;
11
+ getAnchorRect: () => TooltipAnchorRect | null;
12
+ }
13
+
14
+ const ellipsisWidthCache = new Map<string, number>();
15
+ let ellipsisMeasureSpan: HTMLSpanElement | null = null;
16
+
17
+ const measureEllipsisWidth = (computed: CSSStyleDeclaration): number => {
18
+ const cacheKey = `${computed.fontStyle}|${computed.fontWeight}|${computed.fontSize}|${computed.fontFamily}|${computed.letterSpacing}`;
19
+ const cached = ellipsisWidthCache.get(cacheKey);
20
+
21
+ if (cached !== undefined) {
22
+ return cached;
23
+ }
24
+
25
+ ellipsisMeasureSpan ??= document.createElement('span');
26
+ const span = ellipsisMeasureSpan;
27
+
28
+ span.textContent = ELLIPSIS;
29
+ span.style.position = 'absolute';
30
+ span.style.visibility = 'hidden';
31
+ span.style.whiteSpace = 'pre';
32
+ span.style.fontStyle = computed.fontStyle;
33
+ span.style.fontWeight = computed.fontWeight;
34
+ span.style.fontSize = computed.fontSize;
35
+ span.style.fontFamily = computed.fontFamily;
36
+ span.style.letterSpacing = computed.letterSpacing;
37
+
38
+ document.body.appendChild(span);
39
+ const width = span.getBoundingClientRect().width;
40
+ span.remove();
41
+
42
+ ellipsisWidthCache.set(cacheKey, width);
43
+ return width;
44
+ };
45
+
46
+ let measureRange: Range | null = null;
47
+
48
+ const measureFittingPrefixRight = (textNode: Text, maxRight: number): number => {
49
+ measureRange ??= document.createRange();
50
+ const range = measureRange;
51
+
52
+ let low = 0;
53
+ let high = textNode.length;
54
+ let fittingLength = 0;
55
+
56
+ while (low <= high) {
57
+ const mid = (low + high) >>> 1;
58
+ range.setStart(textNode, 0);
59
+ range.setEnd(textNode, mid);
60
+
61
+ if (range.getBoundingClientRect().right <= maxRight) {
62
+ fittingLength = mid;
63
+ low = mid + 1;
64
+ } else {
65
+ high = mid - 1;
66
+ }
67
+ }
68
+
69
+ range.setStart(textNode, 0);
70
+ range.setEnd(textNode, fittingLength);
71
+ return range.getBoundingClientRect().right;
72
+ };
73
+
74
+ const getFirstTextNode = (element: HTMLElement): Text | null =>
75
+ element.firstChild instanceof Text ? element.firstChild : null;
76
+
77
+ export const useTruncation = (
78
+ ref: RefObject<HTMLElement | null>,
79
+ enabled: boolean,
80
+ deps: readonly unknown[] = [],
81
+ ): Truncation => {
82
+ const [isTruncated, setIsTruncated] = useState(false);
83
+
84
+ useLayoutEffect(() => {
85
+ const element = ref.current;
86
+
87
+ if (!enabled || !element) {
88
+ setIsTruncated(false);
89
+ return;
90
+ }
91
+
92
+ const measure = () => {
93
+ const overflowsHorizontally = element.scrollWidth - element.clientWidth > OVERFLOW_TOLERANCE_PX;
94
+ const overflowsVertically = element.scrollHeight - element.clientHeight > OVERFLOW_TOLERANCE_PX;
95
+
96
+ setIsTruncated(overflowsHorizontally || overflowsVertically);
97
+ };
98
+
99
+ const rafId = requestAnimationFrame(measure);
100
+
101
+ const resizeObserver = new ResizeObserver(() => requestAnimationFrame(measure));
102
+ resizeObserver.observe(element);
103
+
104
+ return () => {
105
+ cancelAnimationFrame(rafId);
106
+ resizeObserver.disconnect();
107
+ };
108
+ // eslint-disable-next-line react-hooks/exhaustive-deps
109
+ }, [ref, enabled, ...deps]);
110
+
111
+ const getAnchorRect = useCallback((): TooltipAnchorRect | null => {
112
+ const element = ref.current;
113
+
114
+ if (!element) {
115
+ return null;
116
+ }
117
+
118
+ const rect = element.getBoundingClientRect();
119
+ const computed = getComputedStyle(element);
120
+
121
+ const paddingRight = parseFloat(computed.paddingRight) || 0;
122
+ const borderRight = parseFloat(computed.borderRightWidth) || 0;
123
+ const paddingBottom = parseFloat(computed.paddingBottom) || 0;
124
+ const borderBottom = parseFloat(computed.borderBottomWidth) || 0;
125
+
126
+ const fontSize = parseFloat(computed.fontSize) || DEFAULT_FONT_SIZE_PX;
127
+ const lineHeight = parseFloat(computed.lineHeight) || fontSize * FALLBACK_LINE_HEIGHT_RATIO;
128
+
129
+ const contentRight = rect.right - paddingRight - borderRight;
130
+ const ellipsisWidth = measureEllipsisWidth(computed);
131
+
132
+ const textNode = getFirstTextNode(element);
133
+ const isSingleLine = computed.whiteSpace === 'nowrap';
134
+
135
+ const ellipsisCenterX =
136
+ isSingleLine && textNode
137
+ ? measureFittingPrefixRight(textNode, contentRight - ellipsisWidth) + ellipsisWidth / 2
138
+ : contentRight - ellipsisWidth / 2;
139
+
140
+ const lastLineBottom = rect.bottom - paddingBottom - borderBottom;
141
+
142
+ return {
143
+ x: ellipsisCenterX,
144
+ y: lastLineBottom - lineHeight,
145
+ width: 0,
146
+ height: lineHeight,
147
+ };
148
+ }, [ref]);
149
+
150
+ return { isTruncated, getAnchorRect };
151
+ };
@@ -0,0 +1,16 @@
1
+ // url=https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=38540-44159
2
+ // source=https://github.com/drivenets/design-system/tree/main/packages/design-system/src/components/ds-user-card
3
+ // component=DsUserCard
4
+ //
5
+ // `DAP_user-menu-header_v01` (#38540:44159) is a plain composed instance with no
6
+ // variant props, so it maps directly to `DsUserCard`. `name` and `subtitle` are text
7
+ // content and `src` is a photo URL — none are derivable from Figma — so they are
8
+ // emitted as placeholders for the developer to fill in (see `ds-select.figma.ts`).
9
+ import figma from 'figma';
10
+
11
+ export default {
12
+ example: figma.code`<DsUserCard name="Full Name" subtitle="subtitle" />`,
13
+ imports: ["import { DsUserCard } from '@drivenets/design-system'"],
14
+ id: 'ds-user-card',
15
+ metadata: { nestable: true },
16
+ };
@@ -0,0 +1,25 @@
1
+ .userCard {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: var(--xs);
5
+ padding: var(--sm);
6
+ border-bottom: 1px solid var(--border-secondary);
7
+ }
8
+
9
+ .text {
10
+ display: flex;
11
+ flex-direction: column;
12
+ flex: 1;
13
+ gap: var(--3xs);
14
+ padding-top: var(--4xs);
15
+ min-width: 0;
16
+ }
17
+
18
+ .name,
19
+ .subtitle {
20
+ display: block;
21
+ max-width: 100%;
22
+ overflow: hidden;
23
+ white-space: nowrap;
24
+ text-overflow: ellipsis;
25
+ }
@@ -0,0 +1,31 @@
1
+ import type { FC } from 'react';
2
+ import classNames from 'classnames';
3
+ import styles from './ds-user-card.module.scss';
4
+ import type { DsUserCardProps } from './ds-user-card.types';
5
+ import { DsAvatar } from '../ds-avatar';
6
+ import { DsTypography } from '../ds-typography';
7
+
8
+ /**
9
+ * @summary Compact identity header pairing an avatar with a name and optional subtitle.
10
+ *
11
+ * Presentational card used as the header of a user menu. Renders a medium
12
+ * `DsAvatar` (initials fallback derived from `name`) beside a two-line text
13
+ * block; both lines truncate with an ellipsis when they overflow.
14
+ */
15
+ export const DsUserCard: FC<DsUserCardProps> = ({ name, subtitle, src, className, style }) => {
16
+ return (
17
+ <div className={classNames(styles.userCard, className)} style={style}>
18
+ <DsAvatar name={name} src={src} size="md" />
19
+ <div className={styles.text}>
20
+ <DsTypography variant="heading4" color="main" className={styles.name}>
21
+ {name}
22
+ </DsTypography>
23
+ {subtitle && (
24
+ <DsTypography variant="body-xs-reg" color="secondary" className={styles.subtitle}>
25
+ {subtitle}
26
+ </DsTypography>
27
+ )}
28
+ </div>
29
+ </div>
30
+ );
31
+ };
@@ -0,0 +1,26 @@
1
+ import type { CSSProperties } from 'react';
2
+
3
+ export interface DsUserCardProps {
4
+ /**
5
+ * Full name shown as the title. Also drives the avatar initials fallback,
6
+ * image alt text, and hover tooltip.
7
+ */
8
+ name: string;
9
+ /**
10
+ * Secondary line rendered under the name, such as an email address or role.
11
+ */
12
+ subtitle?: string;
13
+ /**
14
+ * Avatar image URL. Falls back to initials derived from `name` when omitted
15
+ * or when the image fails to load.
16
+ */
17
+ src?: string;
18
+ /**
19
+ * Custom class name applied to the root element.
20
+ */
21
+ className?: string;
22
+ /**
23
+ * Custom inline styles applied to the root element.
24
+ */
25
+ style?: CSSProperties;
26
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ds-user-card';
2
+ export * from './ds-user-card.types';
@@ -73,8 +73,14 @@ const DsVerticalTabsContent = ({ value, className, children }: DsVerticalTabsCon
73
73
  );
74
74
  };
75
75
 
76
+ DsVerticalTabsList.displayName = 'DsVerticalTabs.List';
77
+ DsVerticalTabsTab.displayName = 'DsVerticalTabs.Tab';
78
+ DsVerticalTabsContent.displayName = 'DsVerticalTabs.Content';
79
+
76
80
  DsVerticalTabs.List = DsVerticalTabsList;
77
81
  DsVerticalTabs.Tab = DsVerticalTabsTab;
78
82
  DsVerticalTabs.Content = DsVerticalTabsContent;
79
83
 
84
+ DsVerticalTabs.displayName = 'DsVerticalTabs';
85
+
80
86
  export default DsVerticalTabs;
@@ -124,6 +124,14 @@ const LeftPanel = ({ className, ...rest }: DsWorkspaceLayoutLeftPanelProps) => {
124
124
  return <aside {...rest} className={classNames(styles.leftPanel, className)} />;
125
125
  };
126
126
 
127
+ Header.displayName = 'DsWorkspaceLayout.Header';
128
+ SubHeader.displayName = 'DsWorkspaceLayout.SubHeader';
129
+ Content.displayName = 'DsWorkspaceLayout.Content';
130
+ Footer.displayName = 'DsWorkspaceLayout.Footer';
131
+ Body.displayName = 'DsWorkspaceLayout.Body';
132
+ SideMenu.displayName = 'DsWorkspaceLayout.SideMenu';
133
+ LeftPanel.displayName = 'DsWorkspaceLayout.LeftPanel';
134
+
127
135
  DsWorkspaceLayout.Header = Header;
128
136
  DsWorkspaceLayout.SubHeader = SubHeader;
129
137
  DsWorkspaceLayout.Content = Content;
@@ -132,4 +140,6 @@ DsWorkspaceLayout.Body = Body;
132
140
  DsWorkspaceLayout.SideMenu = SideMenu;
133
141
  DsWorkspaceLayout.LeftPanel = LeftPanel;
134
142
 
143
+ DsWorkspaceLayout.displayName = 'DsWorkspaceLayout';
144
+
135
145
  export default DsWorkspaceLayout;
package/src/index.ts CHANGED
@@ -43,6 +43,7 @@ export * from './components/ds-progress-task-bar';
43
43
  export * from './components/ds-radio-group';
44
44
  export * from './components/ds-segment-group';
45
45
  export * from './components/ds-select';
46
+ export * from './components/ds-site-node';
46
47
  export * from './components/ds-skeleton';
47
48
  export * from './components/ds-smart-tabs';
48
49
  export * from './components/ds-spinner';
@@ -64,6 +65,7 @@ export * from './components/ds-toggle';
64
65
  export * from './components/ds-tooltip';
65
66
  export * from './components/ds-tree';
66
67
  export * from './components/ds-typography';
68
+ export * from './components/ds-user-card';
67
69
  export * from './components/ds-vertical-tabs';
68
70
  export * from './components/ds-workspace-layout';
69
71
  export * from './utils/responsive';