addon-ui 0.4.2 → 0.6.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 (53) hide show
  1. package/dist-types/components/Avatar/types.d.ts +1 -0
  2. package/dist-types/components/Button/types.d.ts +3 -1
  3. package/dist-types/components/Tabs/Tabs.d.ts +8 -0
  4. package/dist-types/components/Tabs/TabsContent.d.ts +6 -0
  5. package/dist-types/components/Tabs/TabsList.d.ts +11 -0
  6. package/dist-types/components/Tabs/TabsTrigger.d.ts +11 -0
  7. package/dist-types/components/Tabs/index.d.ts +6 -0
  8. package/dist-types/components/Tabs/types.d.ts +6 -0
  9. package/dist-types/components/Toast/Toast.d.ts +3 -1
  10. package/dist-types/components/Toast/types.d.ts +4 -0
  11. package/dist-types/components/Truncate/Truncate.d.ts +9 -0
  12. package/dist-types/components/Truncate/index.d.ts +2 -0
  13. package/dist-types/components/TruncateList/TruncateList.d.ts +13 -0
  14. package/dist-types/components/TruncateList/index.d.ts +2 -0
  15. package/dist-types/components/Viewport/Provider.d.ts +1 -0
  16. package/dist-types/components/Viewport/context.d.ts +1 -0
  17. package/dist-types/components/index.d.ts +3 -0
  18. package/dist-types/components/types.d.ts +8 -2
  19. package/package.json +8 -6
  20. package/src/components/Avatar/avatar.module.scss +4 -0
  21. package/src/components/Avatar/types.ts +1 -0
  22. package/src/components/Button/button.module.scss +32 -4
  23. package/src/components/Button/types.ts +2 -0
  24. package/src/components/List/list.module.scss +2 -1
  25. package/src/components/ListItem/list-item.module.scss +1 -1
  26. package/src/components/ScrollArea/ScrollArea.tsx +9 -2
  27. package/src/components/ScrollArea/scroll-area.module.scss +12 -3
  28. package/src/components/Tabs/Tabs.tsx +32 -0
  29. package/src/components/Tabs/TabsContent.tsx +21 -0
  30. package/src/components/Tabs/TabsList.tsx +131 -0
  31. package/src/components/Tabs/TabsTrigger.tsx +43 -0
  32. package/src/components/Tabs/index.ts +5 -0
  33. package/src/components/Tabs/tabs.module.scss +166 -0
  34. package/src/components/Tabs/types.ts +5 -0
  35. package/src/components/Tag/tag.module.scss +10 -7
  36. package/src/components/TextField/text-field.module.scss +6 -2
  37. package/src/components/Toast/Toast.tsx +7 -1
  38. package/src/components/Toast/toast.module.scss +56 -13
  39. package/src/components/Toast/types.ts +5 -0
  40. package/src/components/Tooltip/Tooltip.tsx +1 -1
  41. package/src/components/Truncate/Truncate.tsx +112 -0
  42. package/src/components/Truncate/index.ts +1 -0
  43. package/src/components/Truncate/truncate.module.scss +20 -0
  44. package/src/components/TruncateList/TruncateList.tsx +47 -0
  45. package/src/components/TruncateList/index.ts +1 -0
  46. package/src/components/TruncateList/truncate-list.module.scss +20 -0
  47. package/src/components/Viewport/Provider.tsx +12 -1
  48. package/src/components/Viewport/context.ts +4 -0
  49. package/src/components/Viewport/viewport.module.scss +10 -7
  50. package/src/components/index.ts +3 -0
  51. package/src/components/types.ts +13 -1
  52. package/src/providers/ui/styles/reset.scss +2 -0
  53. package/src/styles/mixins.scss +6 -0
@@ -0,0 +1,43 @@
1
+ import React, {forwardRef, ForwardRefRenderFunction, memo} from "react";
2
+ import classnames from "classnames";
3
+
4
+ import {TabsTriggerProps as TabsTriggerRadixProps, Trigger} from "@radix-ui/react-tabs";
5
+ import {useComponentProps} from "../../providers";
6
+
7
+ import styles from "./tabs.module.scss";
8
+
9
+ export interface TabsTriggerProps extends TabsTriggerRadixProps {
10
+ before?: number | string;
11
+ after?: number | string;
12
+ afterClassname?: string;
13
+ beforeClassname?: string;
14
+ }
15
+
16
+ const TabsTrigger: ForwardRefRenderFunction<HTMLButtonElement, TabsTriggerProps> = (props, ref) => {
17
+ const {
18
+ after,
19
+ before,
20
+ className,
21
+ afterClassname,
22
+ beforeClassname,
23
+ children,
24
+ asChild = true,
25
+ ...other
26
+ } = {...useComponentProps("tabsTrigger"), ...props};
27
+
28
+ return (
29
+ <Trigger ref={ref} asChild={asChild} className={classnames(styles["tabs__trigger"], className)} {...other}>
30
+ <div className={classnames(styles["tabs__trigger-wrapper"])}>
31
+ {before && (
32
+ <span className={classnames(styles["tabs__trigger__before"], beforeClassname)}>{before}</span>
33
+ )}
34
+
35
+ {typeof children === "string" ? <span>{children}</span> : children}
36
+
37
+ {after && <span className={classnames(styles["tabs__trigger__after"], afterClassname)}>{after}</span>}
38
+ </div>
39
+ </Trigger>
40
+ );
41
+ };
42
+
43
+ export default memo(forwardRef(TabsTrigger));
@@ -0,0 +1,5 @@
1
+ export * from "./types";
2
+ export {default as Tabs, type TabsProps} from "./Tabs";
3
+ export {default as TabsList, type TabsListProps} from "./TabsList";
4
+ export {default as TabsTrigger, type TabsTriggerProps} from "./TabsTrigger";
5
+ export {default as TabsContent, type TabsContentProps} from "./TabsContent";
@@ -0,0 +1,166 @@
1
+ @use "../../styles/mixins" as theme;
2
+
3
+ $root: tabs;
4
+ $radius: var(--tabs-indicator-radius, 10px);
5
+ $border-width: 1px;
6
+
7
+ .#{$root} {
8
+ flex: 1;
9
+ width: 100%;
10
+ display: flex;
11
+ flex-direction: column;
12
+ overflow: hidden;
13
+
14
+ &--reverse {
15
+ flex-direction: column-reverse;
16
+ }
17
+
18
+ &__list {
19
+ position: relative;
20
+ display: flex;
21
+ justify-content: space-between;
22
+ transition: border-bottom-color var(--transition-speed-sm);
23
+
24
+ @include theme.rtl() {
25
+ & {
26
+ flex-direction: row-reverse;
27
+ }
28
+ }
29
+
30
+ &--separator {
31
+ border-bottom: var(--tabs-list-border-width, #{$border-width}) solid
32
+ var(--tabs-list-border-color, var(--separator-color));
33
+
34
+ .#{$root}--reverse & {
35
+ border-bottom: none;
36
+ border-top: var(--tabs-list-border-width, #{$border-width}) solid
37
+ var(--tabs-list-border-color, var(--separator-color));
38
+ }
39
+ }
40
+ }
41
+
42
+ &__trigger {
43
+ display: flex;
44
+ justify-content: center;
45
+ align-items: center;
46
+ width: 100%;
47
+ align-self: center;
48
+ text-align: center;
49
+ cursor: pointer;
50
+ user-select: none;
51
+ padding: var(--tabs-trigger-padding, 10px) 0;
52
+ height: var(--tabs-trigger-height, 40px);
53
+ font-size: var(--tabs-trigger-font-size, 14px);
54
+ font-family: var(--tabs-trigger-font-family, var(--font-family)), sans-serif;
55
+ font-weight: var(--tabs-trigger-font-weight, 400);
56
+ color: var(--tabs-trigger-color, var(--text-primary-color));
57
+ transition:
58
+ color var(--transition-speed-sm) ease,
59
+ font-weight var(--transition-speed-sm) ease;
60
+
61
+ &[data-state="active"] {
62
+ font-weight: var(--tabs-trigger-font-weight-active, 600);
63
+ }
64
+
65
+ &[data-disabled] {
66
+ cursor: default;
67
+ opacity: var(--tabs-trigger-dissabled-opacity, 0.6);
68
+ }
69
+
70
+ &__before,
71
+ &__after {
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ min-width: var(--tabs-trigger-badge-min-width, 18px);
76
+ font-size: var(--tabs-trigger-badge-font-size, 12px);
77
+ font-weight: var(--tabs-trigger-badge-font-weight, 700);
78
+ padding: var(--tabs-trigger-badge-padding, 2px 4px);
79
+ border-radius: var(--tabs-trigger-badge-radius, 4px);
80
+ color: var(--tabs-trigger-badge-color, var(--text-secondary-color));
81
+ background-color: var(--tabs-trigger-badge-bg-color, var(--bg-secondary-color));
82
+ transition:
83
+ background-color var(--transition-speed-sm) ease,
84
+ color var(--transition-speed-sm) ease;
85
+
86
+ .#{$root}__trigger[data-state="active"] & {
87
+ color: var(--tabs-trigger-badge-active-color, white);
88
+ background-color: var(--tabs-trigger-badge-active-bg-color, var(--primary-color));
89
+ }
90
+ }
91
+
92
+ &-wrapper {
93
+ display: flex;
94
+ justify-content: center;
95
+ align-items: center;
96
+ width: 100%;
97
+ gap: var(--tabs-trigger-gap, 4px);
98
+ }
99
+ }
100
+
101
+ &__indicator {
102
+ opacity: 0;
103
+ position: absolute;
104
+ bottom: calc(-1 * var(--tabs-list-border-width, #{$border-width}));
105
+ height: var(--tabs-indicator-size, 4px);
106
+ background-color: var(--tabs-indicator-color, var(--primary-color));
107
+ border-radius: #{$radius} #{$radius} 0 0;
108
+ will-change: opacity, left, width;
109
+ transition:
110
+ opacity var(--transition-speed-sm) ease,
111
+ left var(--transition-speed-sm) ease,
112
+ width var(--transition-speed-sm) ease;
113
+
114
+ .#{$root}--reverse & {
115
+ bottom: auto;
116
+ top: calc(-1 * var(--tabs-list-border-width, #{$border-width}));
117
+ border-radius: 0 0 #{$radius} #{$radius};
118
+ }
119
+
120
+ &--show {
121
+ opacity: 1;
122
+ }
123
+
124
+ &--first {
125
+ @include theme.ltr() {
126
+ &:not(.#{$root}__indicator--rounded_edges) {
127
+ border-top-left-radius: 0;
128
+ border-bottom-left-radius: 0;
129
+ }
130
+ }
131
+
132
+ @include theme.rtl() {
133
+ &:not(.#{$root}__indicator--rounded_edges) {
134
+ border-top-right-radius: 0;
135
+ border-bottom-right-radius: 0;
136
+ }
137
+ }
138
+ }
139
+
140
+ &--last {
141
+ @include theme.ltr() {
142
+ &:not(.#{$root}__indicator--rounded_edges) {
143
+ border-top-right-radius: 0;
144
+ border-bottom-right-radius: 0;
145
+ }
146
+ }
147
+
148
+ @include theme.rtl() {
149
+ &:not(.#{$root}__indicator--rounded_edges) {
150
+ border-top-left-radius: 0;
151
+ border-bottom-left-radius: 0;
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ &__content {
158
+ overflow: hidden;
159
+ display: flex;
160
+ flex-direction: column;
161
+
162
+ &[data-state="active"] {
163
+ flex: 1;
164
+ }
165
+ }
166
+ }
@@ -0,0 +1,5 @@
1
+ export enum TabsColor {
2
+ Primary = "primary",
3
+ Secondary = "secondary",
4
+ Accent = "accent",
5
+ }
@@ -49,21 +49,24 @@ $root: tag;
49
49
  color: var(--tag-outlined-color, var(--tag-color, var(--text-secondary-color)));
50
50
  border: var(--tag-border-width, 1px) solid
51
51
  var(--tag-outlined-border-color, var(--tag-outlined-color, var(--text-secondary-color)));
52
- background: none;
52
+ background: var(--tag-outlined-bg-color, none);
53
53
 
54
54
  &.#{$root}--primary-color {
55
- color: var(--primary-color);
56
- border-color: var(--tag-outlined-border-primary-color, var(--primary-color));
55
+ color: var(--tag-outlined-color-primary-color, var(--primary-color));
56
+ border-color: var(--tag-outlined-border-color-primary-color, var(--primary-color));
57
+ background: var(--tag-outlined-bg-color-primary-color, none);
57
58
  }
58
59
 
59
60
  &.#{$root}--secondary-color {
60
- color: var(--secondary-color);
61
- border-color: var(--tag-outlined-border-secondary-color, var(--secondary-color));
61
+ color: var(--tag-outlined-color-secondary-color, var(--secondary-color));
62
+ border-color: var(--tag-outlined-border-color-secondary-color, var(--secondary-color));
63
+ background: var(--tag-outlined-bg-color-secondary-color, none);
62
64
  }
63
65
 
64
66
  &.#{$root}--accent-color {
65
- color: var(--accent-color);
66
- border-color: var(--tag-outlined-border-accent-color, var(--accent-color));
67
+ color: var(--tag-outlined-color-accent-color, var(--accent-color));
68
+ border-color: var(--tag-outlined-border-color-accent-color, var(--accent-color));
69
+ background: var(--tag-outlined-bg-color-accent-color, none);
67
70
  }
68
71
  }
69
72
 
@@ -43,6 +43,10 @@ $root: text-field;
43
43
  &:disabled {
44
44
  cursor: not-allowed;
45
45
  }
46
+
47
+ &::placeholder {
48
+ color: var(--text-field-placeholder-color, var(--text-secondary-color));
49
+ }
46
50
  }
47
51
 
48
52
  &__before,
@@ -107,12 +111,12 @@ $root: text-field;
107
111
 
108
112
  &--medium-size {
109
113
  padding: var(--text-field-padding-md, 10px 14px);
110
- font-size: var(--text-field-font-size-sm, 16px);
114
+ font-size: var(--text-field-font-size-md, 16px);
111
115
  }
112
116
 
113
117
  &--large-size {
114
118
  padding: var(--text-field-padding-lg, 12px 16px);
115
- font-size: var(--text-field-font-size-sm, 18px);
119
+ font-size: var(--text-field-font-size-lg, 18px);
116
120
  }
117
121
 
118
122
  // Accents
@@ -14,7 +14,7 @@ import {IconButton, IconButtonProps} from "../IconButton";
14
14
  import {cloneOrCreateElement} from "../../utils";
15
15
  import {useComponentProps} from "../../providers";
16
16
 
17
- import {ToastSide, ToastRadius, ToastColor} from "./types";
17
+ import {ToastSide, ToastRadius, ToastColor, ToastAnimation} from "./types";
18
18
 
19
19
  import styles from "./toast.module.scss";
20
20
 
@@ -33,6 +33,8 @@ export interface ToastProps extends Omit<ToastRootProps, "title">, Omit<ToastPro
33
33
  radius?: ToastRadius;
34
34
  title?: ReactNode;
35
35
  action?: ReactNode;
36
+ animationIn?: ToastAnimation;
37
+ animationOut?: ToastAnimation;
36
38
  description?: ReactNode;
37
39
  closeIcon?: ReactElement;
38
40
  closeProps?: IconButtonProps;
@@ -54,6 +56,8 @@ const Toast: ForwardRefRenderFunction<HTMLLIElement, ToastProps> = (props, ref)
54
56
  radius,
55
57
  title,
56
58
  action,
59
+ animationIn = ToastAnimation.Slide,
60
+ animationOut = ToastAnimation.Slide,
57
61
  description,
58
62
  fullWidth,
59
63
  sticky,
@@ -87,6 +91,8 @@ const Toast: ForwardRefRenderFunction<HTMLLIElement, ToastProps> = (props, ref)
87
91
  [styles[`toast--${side}`]]: side,
88
92
  [styles[`toast--${color}-color`]]: color,
89
93
  [styles[`toast--${radius}-radius`]]: radius,
94
+ [styles[`toast--${animationIn}-animation-in`]]: animationIn,
95
+ [styles[`toast--${animationOut}-animation-out`]]: animationOut,
90
96
  [styles["toast--sticky"]]: sticky,
91
97
  [styles["toast--full-width"]]: fullWidth,
92
98
  },
@@ -15,27 +15,55 @@ $root: toast;
15
15
  padding: var(--toast-padding, var(--side-padding-xs));
16
16
 
17
17
  &[data-state="open"] {
18
- &.#{$root}--top-left,
19
- &.#{$root}--bottom-left {
20
- animation: slideInLeft var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
18
+ &.#{$root}--opacity-animation-in {
19
+ animation: show var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
21
20
  }
22
21
 
23
- &.#{$root}--top-right,
24
- &.#{$root}--bottom-right {
25
- animation: slideInRight var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
26
- }
22
+ &.#{$root}--slide-animation-in {
23
+ &.#{$root}--top-left,
24
+ &.#{$root}--bottom-left {
25
+ animation: slideInLeft var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
26
+ }
27
27
 
28
- &.#{$root}--top-center {
29
- animation: slideInTop var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
30
- }
28
+ &.#{$root}--top-right,
29
+ &.#{$root}--bottom-right {
30
+ animation: slideInRight var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
31
+ }
31
32
 
32
- &.#{$root}--bottom-center {
33
- animation: slideInBottom var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
33
+ &.#{$root}--top-center {
34
+ animation: slideInTop var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
35
+ }
36
+
37
+ &.#{$root}--bottom-center {
38
+ animation: slideInBottom var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
39
+ }
34
40
  }
35
41
  }
36
42
 
37
43
  &[data-state="closed"] {
38
- animation: hide var(--toast-transition-speed, var(--transition-speed-md)) ease-in;
44
+ &.#{$root}--opacity-animation-out {
45
+ animation: hide var(--toast-transition-speed, var(--transition-speed-md)) ease-in;
46
+ }
47
+
48
+ &.#{$root}--slide-animation-out {
49
+ &.#{$root}--top-left,
50
+ &.#{$root}--bottom-left {
51
+ animation: swipeOutLeft var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
52
+ }
53
+
54
+ &.#{$root}--top-right,
55
+ &.#{$root}--bottom-right {
56
+ animation: swipeOutRight var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
57
+ }
58
+
59
+ &.#{$root}--top-center {
60
+ animation: swipeOutTop var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
61
+ }
62
+
63
+ &.#{$root}--bottom-center {
64
+ animation: swipeOutBottom var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
65
+ }
66
+ }
39
67
  }
40
68
 
41
69
  &[data-swipe="move"] {
@@ -52,6 +80,7 @@ $root: toast;
52
80
  &.#{$root}--bottom-left {
53
81
  animation: swipeOutLeft var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
54
82
  }
83
+
55
84
  &.#{$root}--top-right,
56
85
  &.#{$root}--bottom-right {
57
86
  animation: swipeOutRight var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
@@ -60,6 +89,7 @@ $root: toast;
60
89
  &.#{$root}--top-center {
61
90
  animation: swipeOutTop var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
62
91
  }
92
+
63
93
  &.#{$root}--bottom-center {
64
94
  animation: swipeOutBottom var(--toast-transition-speed, var(--transition-speed-md)) ease-out;
65
95
  }
@@ -68,12 +98,15 @@ $root: toast;
68
98
  &--none-radius {
69
99
  border-radius: 0;
70
100
  }
101
+
71
102
  &--small-radius {
72
103
  border-radius: var(--toast-border-radius, 6px);
73
104
  }
105
+
74
106
  &--medium-radius {
75
107
  border-radius: var(--toast-border-radius, 15px);
76
108
  }
109
+
77
110
  &--large-radius {
78
111
  border-radius: var(--toast-border-radius, 20px);
79
112
  }
@@ -91,6 +124,7 @@ $root: toast;
91
124
  .#{$root}--error-color & {
92
125
  color: var(--toast-error-text-color, white);
93
126
  }
127
+
94
128
  .#{$root}--success-color & {
95
129
  color: var(--toast-success-text-color, white);
96
130
  }
@@ -194,6 +228,15 @@ $root: toast;
194
228
  }
195
229
  }
196
230
 
231
+ @keyframes show {
232
+ from {
233
+ opacity: 0;
234
+ }
235
+ to {
236
+ opacity: 1;
237
+ }
238
+ }
239
+
197
240
  @keyframes slideInRight {
198
241
  from {
199
242
  transform: translateX(100%);
@@ -18,3 +18,8 @@ export enum ToastColor {
18
18
  Error = "error",
19
19
  Success = "success",
20
20
  }
21
+
22
+ export enum ToastAnimation {
23
+ Slide = "slide",
24
+ Opacity = "opacity",
25
+ }
@@ -29,7 +29,7 @@ const Tooltip: ForwardRefRenderFunction<HTMLDivElement, TooltipProps> = (props,
29
29
  open,
30
30
  defaultOpen,
31
31
  disableHoverableContent,
32
- delayDuration,
32
+ delayDuration = 250,
33
33
  onOpenChange,
34
34
 
35
35
  arrowWidth,
@@ -0,0 +1,112 @@
1
+ import React, {
2
+ ComponentProps,
3
+ forwardRef,
4
+ ForwardRefRenderFunction,
5
+ memo,
6
+ useImperativeHandle,
7
+ useLayoutEffect,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+ import classnames from "classnames";
12
+
13
+ import {useComponentProps} from "../../providers";
14
+
15
+ import styles from "./truncate.module.scss";
16
+
17
+ export interface TruncateProps extends ComponentProps<"span"> {
18
+ text?: string;
19
+ middle?: boolean;
20
+ separator?: string;
21
+ }
22
+
23
+ const trimMiddle = (el: HTMLElement, text: string, separator: string) => {
24
+ const measure = (txt: string) => {
25
+ el.textContent = txt;
26
+ return el.scrollWidth <= el.clientWidth;
27
+ };
28
+
29
+ if (measure(text)) return text;
30
+
31
+ let low = 0;
32
+ let high = text.length - 2;
33
+ let result = "";
34
+
35
+ while (low <= high) {
36
+ const size = Math.floor((low + high) / 2);
37
+ const left = text.slice(0, Math.ceil(size / 2));
38
+ const right = text.slice(text.length - Math.floor(size / 2));
39
+ const trimmed = left + separator + right;
40
+
41
+ if (measure(trimmed)) {
42
+ result = trimmed;
43
+ low = size + 1;
44
+ } else {
45
+ high = size - 1;
46
+ }
47
+ }
48
+
49
+ return result || text.charAt(0) + separator + text.charAt(text.length - 1);
50
+ };
51
+
52
+ const Truncate: ForwardRefRenderFunction<HTMLSpanElement, TruncateProps> = (props, ref) => {
53
+ const {text = "", middle, separator = "...", className, ...other} = {...useComponentProps("truncate"), ...props};
54
+
55
+ const innerRef = useRef<HTMLSpanElement | null>(null);
56
+ const [displayedText, setDisplayedText] = useState(text);
57
+
58
+ useImperativeHandle(ref, () => innerRef.current!, []);
59
+
60
+ useLayoutEffect(() => {
61
+ const el = innerRef.current;
62
+ if (!el || !middle) return;
63
+
64
+ let animationFrameId: number;
65
+ let observer: ResizeObserver | null = null;
66
+
67
+ const measureAndTrim = () => {
68
+ animationFrameId = requestAnimationFrame(() => {
69
+ const newText = trimMiddle(el, text, separator);
70
+ if (newText !== displayedText) {
71
+ setDisplayedText(newText);
72
+ }
73
+ });
74
+ };
75
+
76
+ measureAndTrim();
77
+
78
+ if ("ResizeObserver" in window) {
79
+ observer = new ResizeObserver(() => {
80
+ cancelAnimationFrame(animationFrameId);
81
+ measureAndTrim();
82
+ });
83
+ observer.observe(el);
84
+ }
85
+
86
+ return () => {
87
+ observer?.disconnect();
88
+ cancelAnimationFrame(animationFrameId);
89
+ };
90
+ // eslint-disable-next-line react-hooks/exhaustive-deps
91
+ }, [text, separator, middle]);
92
+
93
+ return (
94
+ <span
95
+ ref={innerRef}
96
+ className={classnames(
97
+ styles["truncate"],
98
+ {
99
+ [styles["truncate--middle"]]: middle,
100
+ },
101
+ className
102
+ )}
103
+ {...other}
104
+ >
105
+ {middle ? displayedText : text}
106
+ </span>
107
+ );
108
+ };
109
+
110
+ Truncate.displayName = "Truncate";
111
+
112
+ export default memo(forwardRef(Truncate));
@@ -0,0 +1 @@
1
+ export {default as Truncate, type TruncateProps} from "./Truncate";
@@ -0,0 +1,20 @@
1
+ @use "../../styles/mixins" as theme;
2
+
3
+ .truncate {
4
+ display: block;
5
+ width: 100%;
6
+ white-space: nowrap;
7
+ overflow: hidden;
8
+ text-overflow: ellipsis;
9
+
10
+ &--middle {
11
+ text-overflow: clip;
12
+
13
+ padding-right: var(--truncate-around-space, 8px);
14
+
15
+ @include theme.rtl() {
16
+ padding-right: 0;
17
+ padding-left: var(--truncate-around-space, 8px);
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,47 @@
1
+ import React, {forwardRef, memo, useCallback} from "react";
2
+ import classnames from "classnames";
3
+ import {OverflowList, OverflowListProps} from "react-responsive-overflow-list";
4
+
5
+ import {useComponentProps} from "../../providers";
6
+
7
+ import styles from "./truncate-list.module.scss";
8
+ import {Tag} from "../Tag";
9
+
10
+ export type TruncateListProps<T = unknown> = OverflowListProps<T> & {
11
+ counterClassName?: string;
12
+ };
13
+
14
+ function TruncateListBase<T>(props: TruncateListProps<T>, ref: React.Ref<HTMLDivElement>) {
15
+ const {renderOverflow, className, counterClassName, ...other} = {...useComponentProps("truncateList"), ...props};
16
+
17
+ const RenderOverflow = useCallback(
18
+ (hiddenItems: T[]) => {
19
+ if (renderOverflow) return renderOverflow(hiddenItems);
20
+
21
+ return (
22
+ <Tag className={classnames(styles["truncate-list__counter"], counterClassName)}>
23
+ {`+${hiddenItems.length}`}
24
+ </Tag>
25
+ );
26
+ },
27
+ [renderOverflow, counterClassName]
28
+ );
29
+
30
+ return (
31
+ <OverflowList
32
+ ref={ref}
33
+ renderOverflow={RenderOverflow}
34
+ className={classnames(styles["truncate-list"], className)}
35
+ {...(other as OverflowListProps<T>)}
36
+ />
37
+ );
38
+ }
39
+
40
+ const TruncateList = memo(forwardRef(TruncateListBase)) as unknown as {
41
+ <T>(props: TruncateListProps<T> & {ref?: React.Ref<HTMLDivElement>}): React.ReactElement | null;
42
+ displayName?: string;
43
+ };
44
+
45
+ TruncateList.displayName = "TruncateList";
46
+
47
+ export default TruncateList;
@@ -0,0 +1 @@
1
+ export {default as TruncateList, type TruncateListProps} from "./TruncateList";
@@ -0,0 +1,20 @@
1
+ @use "../../styles/mixins" as theme;
2
+
3
+ .truncate-list {
4
+ gap: var(--truncate-list-gap, 8px);
5
+
6
+ @include theme.rtl() {
7
+ flex-direction: row-reverse;
8
+ }
9
+
10
+ &__counter {
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ border-radius: var(--truncate-list-counter-br-radius, 9999px);
15
+ background: var(--truncate-list-counter-bg-color, var(--bg-secondary-color));
16
+ padding: var(--truncate-list-counter-padding, 0px 4px);
17
+ color: var(--truncate-list-counter-color, var(--text-secondary-color));
18
+ font-size: var(--truncate-list-counter-font-size, inherit);
19
+ }
20
+ }