@snack-uikit/tag 0.4.10-preview-104ad22c.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 (78) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/LICENSE +201 -0
  3. package/README.md +31 -0
  4. package/dist/components/Tag/Tag.d.ts +20 -0
  5. package/dist/components/Tag/Tag.js +24 -0
  6. package/dist/components/Tag/constants.d.ts +4 -0
  7. package/dist/components/Tag/constants.js +5 -0
  8. package/dist/components/Tag/index.d.ts +1 -0
  9. package/dist/components/Tag/index.js +1 -0
  10. package/dist/components/Tag/styles.module.css +270 -0
  11. package/dist/components/TagRow/TagRow.d.ts +22 -0
  12. package/dist/components/TagRow/TagRow.js +25 -0
  13. package/dist/components/TagRow/constants.d.ts +5 -0
  14. package/dist/components/TagRow/constants.js +5 -0
  15. package/dist/components/TagRow/index.d.ts +1 -0
  16. package/dist/components/TagRow/index.js +1 -0
  17. package/dist/components/TagRow/utils.d.ts +2 -0
  18. package/dist/components/TagRow/utils.js +13 -0
  19. package/dist/components/index.d.ts +2 -0
  20. package/dist/components/index.js +2 -0
  21. package/dist/constants.d.ts +15 -0
  22. package/dist/constants.js +17 -0
  23. package/dist/helperComponents/TagList/TagList.d.ts +12 -0
  24. package/dist/helperComponents/TagList/TagList.js +12 -0
  25. package/dist/helperComponents/TagList/index.d.ts +1 -0
  26. package/dist/helperComponents/TagList/index.js +1 -0
  27. package/dist/helperComponents/TagMore/TagMore.d.ts +12 -0
  28. package/dist/helperComponents/TagMore/TagMore.js +10 -0
  29. package/dist/helperComponents/TagMore/index.d.ts +1 -0
  30. package/dist/helperComponents/TagMore/index.js +1 -0
  31. package/dist/helperComponents/TagMore/styles.module.css +69 -0
  32. package/dist/helperComponents/TagRowSimple/TagRowSimple.d.ts +12 -0
  33. package/dist/helperComponents/TagRowSimple/TagRowSimple.js +20 -0
  34. package/dist/helperComponents/TagRowSimple/index.d.ts +1 -0
  35. package/dist/helperComponents/TagRowSimple/index.js +1 -0
  36. package/dist/helperComponents/TagRowSimple/styles.module.css +10 -0
  37. package/dist/helperComponents/TagRowTruncated/TagRowTruncated.d.ts +13 -0
  38. package/dist/helperComponents/TagRowTruncated/TagRowTruncated.js +87 -0
  39. package/dist/helperComponents/TagRowTruncated/hooks.d.ts +3 -0
  40. package/dist/helperComponents/TagRowTruncated/hooks.js +17 -0
  41. package/dist/helperComponents/TagRowTruncated/index.d.ts +1 -0
  42. package/dist/helperComponents/TagRowTruncated/index.js +1 -0
  43. package/dist/helperComponents/TagRowTruncated/styles.module.css +47 -0
  44. package/dist/helperComponents/TagRowTruncated/utils.d.ts +2 -0
  45. package/dist/helperComponents/TagRowTruncated/utils.js +4 -0
  46. package/dist/helperComponents/index.d.ts +2 -0
  47. package/dist/helperComponents/index.js +2 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +1 -0
  50. package/dist/types.d.ts +9 -0
  51. package/dist/types.js +1 -0
  52. package/package.json +42 -0
  53. package/src/components/Tag/Tag.tsx +55 -0
  54. package/src/components/Tag/constants.ts +6 -0
  55. package/src/components/Tag/index.ts +1 -0
  56. package/src/components/Tag/styles.module.scss +175 -0
  57. package/src/components/TagRow/TagRow.tsx +34 -0
  58. package/src/components/TagRow/constants.ts +5 -0
  59. package/src/components/TagRow/index.ts +1 -0
  60. package/src/components/TagRow/utils.ts +16 -0
  61. package/src/components/index.ts +2 -0
  62. package/src/constants.ts +16 -0
  63. package/src/helperComponents/TagList/TagList.tsx +32 -0
  64. package/src/helperComponents/TagList/index.ts +1 -0
  65. package/src/helperComponents/TagMore/TagMore.tsx +54 -0
  66. package/src/helperComponents/TagMore/index.ts +1 -0
  67. package/src/helperComponents/TagMore/styles.module.scss +61 -0
  68. package/src/helperComponents/TagRowSimple/TagRowSimple.tsx +25 -0
  69. package/src/helperComponents/TagRowSimple/index.ts +1 -0
  70. package/src/helperComponents/TagRowSimple/styles.module.scss +14 -0
  71. package/src/helperComponents/TagRowTruncated/TagRowTruncated.tsx +127 -0
  72. package/src/helperComponents/TagRowTruncated/hooks.ts +23 -0
  73. package/src/helperComponents/TagRowTruncated/index.ts +1 -0
  74. package/src/helperComponents/TagRowTruncated/styles.module.scss +57 -0
  75. package/src/helperComponents/TagRowTruncated/utils.ts +7 -0
  76. package/src/helperComponents/index.ts +2 -0
  77. package/src/index.ts +1 -0
  78. package/src/types.ts +11 -0
@@ -0,0 +1,61 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-tag';
2
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-dropList';
4
+
5
+ $sizes: 'xs', 's';
6
+
7
+ $label-typography: (
8
+ 'xs': $sans-label-s,
9
+ 's': $sans-label-m
10
+ );
11
+
12
+ .button {
13
+ cursor: pointer;
14
+
15
+ display: inline-flex;
16
+ align-items: center;
17
+
18
+ box-sizing: border-box;
19
+
20
+ color: $sys-neutral-text-support;
21
+ white-space: nowrap;
22
+
23
+ background: none;
24
+ border-color: $sys-neutral-decor-disabled;
25
+ border-style: solid;
26
+
27
+ &:focus-visible {
28
+ @include outline-var($container-focused-s);
29
+
30
+ outline-color: $sys-available-complementary;
31
+ }
32
+
33
+ @each $size in $sizes {
34
+ @include composite-var($label-typography, $size);
35
+
36
+ &[data-size=#{$size}] {
37
+ @include composite-var($tag-more, 'container', $size, 'label-only');
38
+ }
39
+ }
40
+ }
41
+
42
+ .tagRowDroplistContainer {
43
+ @include composite-var($drop-list-container);
44
+
45
+ background-color: $sys-neutral-background2-level;
46
+ box-shadow: $box-shadow-elevation-level4;
47
+
48
+ @each $size in $sizes {
49
+ &[data-size=#{$size}] {
50
+ @include composite-var($tag-list, $size);
51
+ }
52
+ }
53
+ }
54
+
55
+ .tagRowDroplistScroll {
56
+ max-height: 200px;
57
+ }
58
+
59
+ .triggerClassName {
60
+ --offset: #{$space-drop-list-drop-offset};
61
+ }
@@ -0,0 +1,25 @@
1
+ import cn from 'classnames';
2
+ import { Ref } from 'react';
3
+
4
+ import { extractSupportProps } from '@snack-uikit/utils';
5
+
6
+ import { Size } from '../../constants';
7
+ import { TagRowItemInner } from '../../types';
8
+ import { TagList } from '../TagList';
9
+ import styles from './styles.module.scss';
10
+
11
+ type TagRowSimpleProps = {
12
+ items: TagRowItemInner[];
13
+ size: Size;
14
+ onItemRemove?(item: string): void;
15
+ setTagRef?(item: TagRowItemInner): Ref<HTMLDivElement>;
16
+ className?: string;
17
+ };
18
+
19
+ export function TagRowSimple({ items, size, onItemRemove, setTagRef, className, ...rest }: TagRowSimpleProps) {
20
+ return (
21
+ <div className={cn(styles.tagRowSimpleWrapper, className)} {...extractSupportProps(rest)} data-size={size}>
22
+ <TagList items={items} size={size} onItemRemove={onItemRemove} setTagRef={setTagRef} />
23
+ </div>
24
+ );
25
+ }
@@ -0,0 +1 @@
1
+ export * from './TagRowSimple';
@@ -0,0 +1,14 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-tag';
2
+
3
+ $sizes: 'xs', 's';
4
+
5
+ .tagRowSimpleWrapper {
6
+ display: flex;
7
+ flex-wrap: wrap;
8
+
9
+ @each $size in $sizes {
10
+ &[data-size=#{$size}] {
11
+ @include composite-var($tag-row, $size);
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,127 @@
1
+ import cn from 'classnames';
2
+ import { useEffect, useRef, useState } from 'react';
3
+
4
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
5
+
6
+ import { TAG_ROW_TEST_IDS } from '../../components/TagRow/constants';
7
+ import { Size } from '../../constants';
8
+ import { TagRowItemInner } from '../../types';
9
+ import { TagList } from '../TagList';
10
+ import { TagMore } from '../TagMore';
11
+ import { useResizeObserver } from './hooks';
12
+ import styles from './styles.module.scss';
13
+ import { getGapWidth } from './utils';
14
+
15
+ type TagRowTruncatedProps = WithSupportProps<{
16
+ items: TagRowItemInner[];
17
+ rowLimit: number;
18
+ moreButtonLabel?: string;
19
+ size: Size;
20
+ className?: string;
21
+ onItemRemove?(item: string): void;
22
+ }>;
23
+
24
+ export function TagRowTruncated({
25
+ items,
26
+ rowLimit,
27
+ size,
28
+ moreButtonLabel = '',
29
+ className,
30
+ onItemRemove,
31
+ ...rest
32
+ }: TagRowTruncatedProps) {
33
+ const [visibleTags, setVisibleTags] = useState<TagRowItemInner[]>([]);
34
+ const [hiddenTags, setHiddenTags] = useState<TagRowItemInner[]>([]);
35
+
36
+ const hiddenRowElementRef = useRef<HTMLDivElement>(null);
37
+ const hiddenMoreButtonRef = useRef<HTMLButtonElement>(null);
38
+ const hiddenMoreButtonWrapperRef = useRef<HTMLDivElement>(null);
39
+
40
+ const [firstTagElement, setFirstTagElement] = useState<HTMLElement | null>(null);
41
+ const tagsMapRef = useRef(new Map<TagRowItemInner, HTMLElement | null>());
42
+
43
+ const { width: maxWidth } = useResizeObserver(hiddenRowElementRef.current);
44
+ const { width: firstVisibleTagWidth } = useResizeObserver(firstTagElement);
45
+
46
+ function setTagElement(tag: TagRowItemInner, index: number) {
47
+ return (tagElement: HTMLElement | null) => {
48
+ if (index === 0 && tagElement) {
49
+ setFirstTagElement(tagElement);
50
+ }
51
+
52
+ if (tagElement === null) {
53
+ tagsMapRef.current.delete(tag);
54
+ } else {
55
+ tagsMapRef.current.set(tag, tagElement);
56
+ }
57
+ };
58
+ }
59
+
60
+ const gapWidth = getGapWidth(hiddenRowElementRef);
61
+
62
+ useEffect(() => {
63
+ if (maxWidth < 1) {
64
+ return;
65
+ }
66
+
67
+ const newVisibleTags: TagRowItemInner[] = [];
68
+ const newHiddenTags: TagRowItemInner[] = [];
69
+
70
+ const moreButtonWidth = hiddenMoreButtonRef.current?.offsetWidth || 0;
71
+
72
+ let currentRowWidth = 0;
73
+ let currentRowCount = 1;
74
+
75
+ tagsMapRef.current.forEach((tagElement, tagRowItem) => {
76
+ const tagWidth = tagElement?.offsetWidth || 0;
77
+ const sumRowWidth = currentRowWidth + tagWidth + gapWidth;
78
+
79
+ if (currentRowCount > rowLimit) {
80
+ newHiddenTags.push(tagRowItem);
81
+ return;
82
+ }
83
+
84
+ if (currentRowCount === rowLimit) {
85
+ if (sumRowWidth + moreButtonWidth > maxWidth) {
86
+ currentRowCount++;
87
+ newHiddenTags.push(tagRowItem);
88
+ return;
89
+ }
90
+
91
+ currentRowWidth = sumRowWidth;
92
+ newVisibleTags.push(tagRowItem);
93
+ return;
94
+ }
95
+
96
+ if (sumRowWidth > maxWidth) {
97
+ currentRowWidth = tagWidth + gapWidth;
98
+ currentRowCount++;
99
+ newVisibleTags.push(tagRowItem);
100
+ return;
101
+ }
102
+
103
+ currentRowWidth = sumRowWidth;
104
+ newVisibleTags.push(tagRowItem);
105
+ });
106
+
107
+ setVisibleTags(newVisibleTags);
108
+ setHiddenTags(newHiddenTags);
109
+ }, [items, rowLimit, maxWidth, onItemRemove, gapWidth, firstVisibleTagWidth]);
110
+
111
+ return (
112
+ <div className={cn(styles.wrapper, className)} {...extractSupportProps(rest)} data-size={size}>
113
+ <div className={styles.hiddenRow} ref={hiddenRowElementRef} data-size={size}>
114
+ <TagList items={items} size={size} onItemRemove={onItemRemove} setTagRef={setTagElement} />
115
+ </div>
116
+ <div className={styles.hiddenMoreButton} ref={hiddenMoreButtonWrapperRef}>
117
+ <TagMore items={items} text={moreButtonLabel} size={size} buttonRef={hiddenMoreButtonRef} />
118
+ </div>
119
+ <div className={styles.visibleRow} data-size={size} data-test-id={TAG_ROW_TEST_IDS.visibleTagsWrapper}>
120
+ <TagList items={visibleTags} size={size} onItemRemove={onItemRemove} />
121
+ {hiddenTags.length > 0 && (
122
+ <TagMore items={hiddenTags} text={moreButtonLabel} size={size} onItemRemove={onItemRemove} />
123
+ )}
124
+ </div>
125
+ </div>
126
+ );
127
+ }
@@ -0,0 +1,23 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export const useResizeObserver = (element: HTMLElement | null) => {
4
+ const [width, setWidth] = useState<number>(0);
5
+
6
+ useEffect(() => {
7
+ if (!element) {
8
+ return;
9
+ }
10
+
11
+ const resizeObserver = new ResizeObserver(() => {
12
+ if (element.offsetWidth !== width) {
13
+ setWidth(element.offsetWidth);
14
+ }
15
+ });
16
+
17
+ resizeObserver.observe(element);
18
+
19
+ return () => resizeObserver.disconnect();
20
+ }, [element, width]);
21
+
22
+ return { width };
23
+ };
@@ -0,0 +1 @@
1
+ export * from './TagRowTruncated';
@@ -0,0 +1,57 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-tag';
2
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-dropList';
3
+
4
+ $sizes: 'xs', 's';
5
+
6
+ .wrapper {
7
+ position: relative;
8
+
9
+ display: flex;
10
+ flex-wrap: wrap;
11
+
12
+ width: 100%;
13
+ max-width: 100%;
14
+
15
+ @each $size in $sizes {
16
+ &[data-size=#{$size}] {
17
+ @include composite-var($tag-row, $size);
18
+ }
19
+ }
20
+ }
21
+
22
+ .visibleRow {
23
+ display: flex;
24
+ flex-wrap: wrap;
25
+ max-width: 100%;
26
+
27
+ @each $size in $sizes {
28
+ &[data-size=#{$size}] {
29
+ @include composite-var($tag-row, $size);
30
+ }
31
+ }
32
+ }
33
+
34
+ .hiddenRow {
35
+ position: absolute;
36
+ top: 0;
37
+ left: 0;
38
+
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+
42
+ width: 100%;
43
+
44
+ visibility: hidden;
45
+
46
+ @each $size in $sizes {
47
+ &[data-size=#{$size}] {
48
+ @include composite-var($tag-row, $size);
49
+ }
50
+ }
51
+ }
52
+
53
+ .hiddenMoreButton {
54
+ position: absolute;
55
+ left: 0;
56
+ visibility: hidden;
57
+ }
@@ -0,0 +1,7 @@
1
+ import { RefObject } from 'react';
2
+
3
+ export const getGapWidth = (elementRef: RefObject<HTMLDivElement>): number => {
4
+ const cssValue = elementRef.current ? getComputedStyle(elementRef.current, null).getPropertyValue('gap') : '0px';
5
+
6
+ return parseInt(cssValue);
7
+ };
@@ -0,0 +1,2 @@
1
+ export * from './TagRowTruncated';
2
+ export * from './TagRowSimple';
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './components';
package/src/types.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Appearance } from './constants';
2
+
3
+ export type TagRowItem = {
4
+ label: string;
5
+ color?: Appearance;
6
+ };
7
+
8
+ export type TagRowItemInner = {
9
+ label: string;
10
+ color: Appearance;
11
+ };