@snack-uikit/list 0.1.3-preview-3f90b7f1.0 → 0.2.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 (38) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +12 -12
  3. package/dist/components/Items/BaseItem/BaseItem.d.ts +3 -3
  4. package/dist/components/Items/BaseItem/BaseItem.js +9 -6
  5. package/dist/components/Items/BaseItem/styles.module.css +3 -0
  6. package/dist/components/Items/NextListItem/NextListItem.js +1 -1
  7. package/dist/components/Items/styles.module.css +10 -10
  8. package/dist/components/Items/types.d.ts +32 -7
  9. package/dist/components/Lists/Droplist/DropList.d.ts +1 -1
  10. package/dist/components/Lists/Droplist/DropList.js +11 -5
  11. package/dist/components/Lists/List/List.d.ts +10 -2
  12. package/dist/components/Lists/List/List.js +25 -11
  13. package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +10 -2
  14. package/dist/components/Lists/ListPrivate/ListPrivate.js +8 -8
  15. package/dist/components/Lists/ListPrivate/styles.module.css +4 -0
  16. package/dist/components/Lists/contexts/SelectionProvider.d.ts +19 -12
  17. package/dist/components/Lists/contexts/SelectionProvider.js +37 -16
  18. package/dist/components/Lists/styles.module.css +4 -0
  19. package/dist/components/Lists/types.d.ts +22 -6
  20. package/dist/helperComponents/HiddenTabButton/HiddenTabButton.d.ts +1 -0
  21. package/dist/helperComponents/HiddenTabButton/HiddenTabButton.js +2 -2
  22. package/dist/utils.js +6 -2
  23. package/package.json +8 -7
  24. package/src/components/Items/BaseItem/BaseItem.tsx +20 -12
  25. package/src/components/Items/BaseItem/styles.module.scss +4 -0
  26. package/src/components/Items/NextListItem/NextListItem.tsx +1 -0
  27. package/src/components/Items/styles.module.scss +17 -15
  28. package/src/components/Items/types.ts +32 -10
  29. package/src/components/Lists/Droplist/DropList.tsx +14 -5
  30. package/src/components/Lists/List/List.tsx +50 -18
  31. package/src/components/Lists/ListPrivate/ListPrivate.tsx +11 -10
  32. package/src/components/Lists/ListPrivate/styles.module.scss +4 -0
  33. package/src/components/Lists/contexts/ListProvider.tsx +0 -2
  34. package/src/components/Lists/contexts/SelectionProvider.tsx +43 -29
  35. package/src/components/Lists/styles.module.scss +5 -0
  36. package/src/components/Lists/types.ts +22 -9
  37. package/src/helperComponents/HiddenTabButton/HiddenTabButton.tsx +12 -2
  38. package/src/utils.ts +11 -2
@@ -18,7 +18,6 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
18
18
  items,
19
19
  pinTop,
20
20
  pinBottom,
21
- collapse = 'multiple',
22
21
  onKeyDown,
23
22
  onBlur,
24
23
  onFocus,
@@ -35,6 +34,8 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
35
34
  noResults = 'No results',
36
35
  size = 's',
37
36
  marker,
37
+ limitedScrollHeight,
38
+ className,
38
39
  ...props
39
40
  },
40
41
  ref,
@@ -63,7 +64,7 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
63
64
 
64
65
  const content = useMemo(
65
66
  () => (
66
- <>
67
+ <div className={styles.content}>
67
68
  {itemsJSX}
68
69
  {loadingJSX}
69
70
  {hasNoItems && !search?.value && !loading && (
@@ -76,14 +77,14 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
76
77
  {noResults}
77
78
  </div>
78
79
  )}
79
- </>
80
+ </div>
80
81
  ),
81
82
  [hasNoItems, itemsJSX, loading, loadingJSX, noData, noResults, search?.value],
82
83
  );
83
84
 
84
85
  const listJSX = (
85
86
  <ul
86
- className={commonStyles.listContainer}
87
+ className={cn(commonStyles.listContainer, className)}
87
88
  ref={ref as ForwardedRef<HTMLUListElement>}
88
89
  onKeyDown={onKeyDown}
89
90
  tabIndex={tabIndex}
@@ -93,7 +94,7 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
93
94
  role='menu'
94
95
  {...extractSupportProps(props)}
95
96
  >
96
- <ToggleGroup selectionMode={collapse}>
97
+ <ToggleGroup selectionMode={'multiple'}>
97
98
  {(Number(pinTop?.length) > 0 || search) && (
98
99
  <PinTopGroupItem>
99
100
  {search && <SearchItem search={search} />}
@@ -105,12 +106,12 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
105
106
  {scroll ? (
106
107
  <Scroll
107
108
  className={cn({
108
- [commonStyles.scrollContainerS]: scroll && size === 's',
109
- [commonStyles.scrollContainerM]: scroll && size === 'm',
110
- [commonStyles.scrollContainerL]: scroll && size === 'l',
109
+ [commonStyles.scrollContainerS]: scroll && limitedScrollHeight && size === 's',
110
+ [commonStyles.scrollContainerM]: scroll && limitedScrollHeight && size === 'm',
111
+ [commonStyles.scrollContainerL]: scroll && limitedScrollHeight && size === 'l',
111
112
  })}
112
- barHideStrategy='never'
113
- size='s'
113
+ barHideStrategy='leave'
114
+ size={size === 's' ? 's' : 'm'}
114
115
  ref={scrollContainerRef}
115
116
  >
116
117
  {content}
@@ -46,3 +46,7 @@ $loader-height: (
46
46
  box-sizing: border-box;
47
47
  padding: $dimension-050m;
48
48
  }
49
+
50
+ .content {
51
+ flex-grow: 1;
52
+ }
@@ -3,8 +3,6 @@ import { createContext, ReactNode, useContext } from 'react';
3
3
  export type ListContextType = {
4
4
  /** Размер списка */
5
5
  size?: 's' | 'm' | 'l';
6
- // TODO
7
- // collapse?: 'single' | 'multiple';
8
6
  /** Отображать ли маркер у выбранного жлемента списка */
9
7
  marker?: boolean;
10
8
  };
@@ -3,7 +3,7 @@ import { useUncontrolledProp } from 'uncontrollable';
3
3
 
4
4
  type SelectionSingleValueType = string | number | undefined;
5
5
 
6
- export type SelectionSingleProps = {
6
+ type SelectionSingleState = {
7
7
  /** Начальное состояние */
8
8
  defaultValue?: SelectionSingleValueType;
9
9
  /** Controlled состояние */
@@ -11,17 +11,20 @@ export type SelectionSingleProps = {
11
11
  /** Controlled обработчик измения состояния */
12
12
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
13
  onChange?(value: any): void;
14
+ /** Режим выбора */
15
+ mode: 'single';
16
+ };
17
+
18
+ export type SelectionSingleProps = {
14
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
20
  setValue?(value: any): void;
16
- /** Режим выбора */
17
- selection: 'single';
18
21
  /** Режим выбора single */
19
22
  isSelectionSingle: true;
20
23
  /** Режим выбора multi */
21
24
  isSelectionMultiple: false;
22
- };
25
+ } & SelectionSingleState;
23
26
 
24
- export type SelectionMultipleProps = {
27
+ type SelectionMultipleState = {
25
28
  /** Начальное состояние */
26
29
  defaultValue?: SelectionSingleValueType[];
27
30
  /** Controlled состояние */
@@ -29,18 +32,21 @@ export type SelectionMultipleProps = {
29
32
  /** Controlled обработчик измения состояния */
30
33
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
34
  onChange?(value: any): void;
35
+ /** Режим выбора */
36
+ mode: 'multiple';
37
+ };
38
+
39
+ export type SelectionMultipleProps = {
32
40
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
41
  setValue?(value: any): void;
34
- /** Режим выбора */
35
- selection: 'multiple';
36
42
  /** Режим выбора single */
37
43
  isSelectionSingle: false;
38
44
  /** Режим выбора multi */
39
45
  isSelectionMultiple: true;
40
- };
46
+ } & SelectionMultipleState;
41
47
 
42
48
  type SelectionNoneProps = {
43
- selection?: undefined;
49
+ mode?: undefined;
44
50
  value?: undefined;
45
51
  onChange?: undefined;
46
52
  setValue?: undefined;
@@ -49,27 +55,29 @@ type SelectionNoneProps = {
49
55
  isSelectionMultiple?: undefined;
50
56
  };
51
57
 
52
- export type SelectionProviderProps = SelectionSingleProps | SelectionMultipleProps | SelectionNoneProps;
58
+ type SelectionProviderProps = SelectionSingleProps | SelectionMultipleProps | SelectionNoneProps;
53
59
 
54
60
  type SelectionContextType =
55
61
  | Omit<SelectionNoneProps, 'defaultValue'>
56
62
  | Omit<SelectionSingleProps, 'defaultValue'>
57
63
  | Omit<SelectionMultipleProps, 'defaultValue'>;
58
64
 
65
+ export type SelectionState = { selection?: SelectionSingleState | SelectionMultipleState };
66
+
59
67
  export const SelectionContext = createContext<SelectionContextType>({
60
68
  value: undefined,
61
69
  onChange: undefined,
62
- selection: undefined,
70
+ mode: undefined,
63
71
  });
64
72
 
65
73
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
74
  export function isSelectionMultipleProps(props: any): props is SelectionMultipleProps {
67
- return 'selection' in props && props['selection'] === 'multiple';
75
+ return 'mode' in props && props['mode'] === 'multiple';
68
76
  }
69
77
 
70
78
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
79
  export function isSelectionSingleProps(props: any): props is SelectionSingleProps {
72
- return 'selection' in props && props['selection'] === 'single';
80
+ return 'mode' in props && props['mode'] === 'single';
73
81
  }
74
82
 
75
83
  type Child = {
@@ -100,7 +108,14 @@ function SelectionSingleProvider({
100
108
 
101
109
  return (
102
110
  <SelectionContext.Provider
103
- value={{ value, onChange, selection: 'single', isSelectionSingle: true, isSelectionMultiple: false, setValue }}
111
+ value={{
112
+ value,
113
+ onChange,
114
+ mode: 'single',
115
+ isSelectionSingle: true,
116
+ isSelectionMultiple: false,
117
+ setValue,
118
+ }}
104
119
  >
105
120
  {children}
106
121
  </SelectionContext.Provider>
@@ -135,37 +150,36 @@ function SelectionMultipleProvider({
135
150
 
136
151
  return (
137
152
  <SelectionContext.Provider
138
- value={{ value, onChange, selection: 'multiple', isSelectionSingle: false, isSelectionMultiple: true, setValue }}
153
+ value={{
154
+ value,
155
+ onChange,
156
+ mode: 'multiple',
157
+ isSelectionSingle: false,
158
+ isSelectionMultiple: true,
159
+ setValue,
160
+ }}
139
161
  >
140
162
  {children}
141
163
  </SelectionContext.Provider>
142
164
  );
143
165
  }
144
166
 
145
- export function SelectionProvider(props: SelectionProviderProps & Child) {
167
+ export function SelectionProvider({ children, ...props }: SelectionProviderProps & Child) {
146
168
  if (isSelectionSingleProps(props)) {
147
- return <SelectionSingleProvider {...props} />;
169
+ return <SelectionSingleProvider {...props}>{children}</SelectionSingleProvider>;
148
170
  }
149
171
 
150
172
  if (isSelectionMultipleProps(props)) {
151
- return <SelectionMultipleProvider {...props} />;
173
+ return <SelectionMultipleProvider {...props}>{children}</SelectionMultipleProvider>;
152
174
  }
153
175
 
154
- return <SelectionContext.Provider value={{}}>{props.children}</SelectionContext.Provider>;
176
+ return <SelectionContext.Provider value={{}}>{children}</SelectionContext.Provider>;
155
177
  }
156
178
 
157
179
  export const useSelectionContext = () => useContext(SelectionContext);
158
180
 
159
- export function extractSelectionProps<T extends SelectionProviderProps>({
160
- selection,
161
- value,
162
- defaultValue,
163
- onChange,
164
- }: T) {
181
+ export function extractSelectionProps<T extends SelectionState>({ selection }: T) {
165
182
  return {
166
- selection,
167
- value,
168
- defaultValue,
169
- onChange,
183
+ ...(selection ?? {}),
170
184
  } as SelectionProviderProps;
171
185
  }
@@ -1,6 +1,10 @@
1
1
  @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
2
2
 
3
3
  .listContainer {
4
+ display: flex;
5
+ flex-direction: column;
6
+
7
+ height: 100%;
4
8
  margin: 0;
5
9
  padding: 0;
6
10
 
@@ -32,6 +36,7 @@
32
36
  display: block;
33
37
 
34
38
  box-sizing: border-box;
39
+ height: 100%;
35
40
  padding: $dimension-025m;
36
41
 
37
42
  &:has(.listContainer:focus-visible) {
@@ -5,7 +5,13 @@ import { WithSupportProps } from '@snack-uikit/utils';
5
5
 
6
6
  import { ScrollProps, SearchState } from '../../types';
7
7
  import { ItemProps } from '../Items';
8
- import { ListContextType, SelectionProviderProps } from './contexts';
8
+ import { ListContextType, SelectionState } from './contexts';
9
+
10
+ type CollapseState = {
11
+ value?: (string | number)[];
12
+ onChange?(value: (string | number)[]): void;
13
+ defaultValue?: (string | number)[];
14
+ };
9
15
 
10
16
  export type ListProps = WithSupportProps<
11
17
  {
@@ -15,12 +21,13 @@ export type ListProps = WithSupportProps<
15
21
  pinTop?: ItemProps[];
16
22
  /** Элементы списка, закрепленные снизу */
17
23
  pinBottom?: ItemProps[];
18
- /** Кастомизируемый элемент, помещаемый внизу списка */
24
+ /**
25
+ * Кастомизируемый элемент в конце списка
26
+ * @type ReactElement
27
+ */
19
28
  footer?: ReactNode;
20
- /** Список ссылок на костомные элементы, помещенные в специальную секцию внизу списка */
29
+ /** Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка */
21
30
  footerActiveElementsRefs?: RefObject<HTMLElement>[];
22
- // TODO: add later
23
- // collapse?: 'single' | 'multiple';
24
31
  /** Настройки поисковой строки */
25
32
  search?: SearchState;
26
33
  /** Флаг, отвещающий за состояние загрузки списка */
@@ -29,7 +36,14 @@ export type ListProps = WithSupportProps<
29
36
  noData?: string;
30
37
  /** Текст для состояния "Отсутсвие результата" при поиске */
31
38
  noResults?: string;
32
- } & Pick<SelectionProviderProps, 'value' | 'defaultValue' | 'onChange' | 'selection'> &
39
+ /** Tab Index */
40
+ tabIndex?: number;
41
+ /** Настройки раскрытия элементов */
42
+ collapse?: CollapseState;
43
+ /** CSS-класс */
44
+ className?: string;
45
+ onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
46
+ } & SelectionState &
33
47
  ListContextType &
34
48
  ScrollProps
35
49
  >;
@@ -40,7 +54,7 @@ export type DroplistProps = {
40
54
  /** Триггер для дроплиста */
41
55
  children?: ReactNode;
42
56
  } & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> &
43
- ListProps;
57
+ Omit<ListProps, 'tabIndex' | 'onKeyDown'>;
44
58
 
45
59
  export type ListPrivateProps = ListProps & {
46
60
  nested?: boolean;
@@ -49,6 +63,5 @@ export type ListPrivateProps = ListProps & {
49
63
  onFocus?(e: FocusEvent<HTMLElement>): void;
50
64
  onBlur?(e: FocusEvent<HTMLElement>): void;
51
65
  onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
52
- // TODO: remove later
53
- collapse?: 'single' | 'multiple';
66
+ limitedScrollHeight?: boolean;
54
67
  };
@@ -4,9 +4,10 @@ import styles from './styles.module.scss';
4
4
 
5
5
  type HiddenTabButtonProps = {
6
6
  listRef: RefObject<HTMLElement>;
7
+ tabIndex?: number;
7
8
  };
8
9
 
9
- export const HiddenTabButton = forwardRef<HTMLButtonElement, HiddenTabButtonProps>(({ listRef }, ref) => {
10
+ export const HiddenTabButton = forwardRef<HTMLButtonElement, HiddenTabButtonProps>(({ listRef, tabIndex }, ref) => {
10
11
  const handleFocus = useCallback(
11
12
  (e: FocusEvent<HTMLButtonElement>) => {
12
13
  if (e.relatedTarget !== listRef.current) {
@@ -23,5 +24,14 @@ export const HiddenTabButton = forwardRef<HTMLButtonElement, HiddenTabButtonProp
23
24
  e.stopPropagation();
24
25
  }, []);
25
26
 
26
- return <button aria-hidden ref={ref} onKeyDown={handleKeyDown} onFocus={handleFocus} className={styles.hiddenBtn} />;
27
+ return (
28
+ <button
29
+ aria-hidden
30
+ ref={ref}
31
+ onKeyDown={handleKeyDown}
32
+ onFocus={handleFocus}
33
+ className={styles.hiddenBtn}
34
+ tabIndex={tabIndex}
35
+ />
36
+ );
27
37
  });
package/src/utils.ts CHANGED
@@ -20,7 +20,10 @@ export function withCollapsedItems({ items, openCollapsedItems }: WithCollapsedI
20
20
  let expandedIds: Array<string | number> = [];
21
21
 
22
22
  items.forEach(item => {
23
- if ((isBaseItemProps(item) || isNextListItemProps(item) || isAccordionItemProps(item)) && !item.disabled) {
23
+ if (
24
+ ((isBaseItemProps(item) && !item.inactive) || isNextListItemProps(item) || isAccordionItemProps(item)) &&
25
+ !item.disabled
26
+ ) {
24
27
  newItems = newItems.concat([item]);
25
28
  ids = ids.concat([item.id ?? '']);
26
29
 
@@ -88,7 +91,13 @@ export function extractItemIds(items: ItemProps[]): Array<string | number> {
88
91
 
89
92
  export function extractChildIds({ items }: { items: ItemProps[] }): Array<string | number> {
90
93
  return items
91
- .filter(item => isAccordionItemProps(item) || isNextListItemProps(item) || isGroupItemProps(item) || !item.disabled)
94
+ .filter(
95
+ item =>
96
+ isAccordionItemProps(item) ||
97
+ isNextListItemProps(item) ||
98
+ isGroupItemProps(item) ||
99
+ (isBaseItemProps(item) && !item.disabled && !item.inactive),
100
+ )
92
101
  .reduce(
93
102
  (prev: Array<string | number>, item: ItemProps) => {
94
103
  if (isAccordionItemProps(item) || isNextListItemProps(item)) {