@cloud-ru/uikit-product-mobile-toolbar 0.4.3

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 (33) hide show
  1. package/CHANGELOG.md +779 -0
  2. package/LICENSE +201 -0
  3. package/README.md +8 -0
  4. package/package.json +58 -0
  5. package/src/components/AdaptiveToolbar/AdaptiveToolbar.tsx +19 -0
  6. package/src/components/AdaptiveToolbar/index.ts +1 -0
  7. package/src/components/MobileToolbar/MobileToolbar.tsx +135 -0
  8. package/src/components/MobileToolbar/helpers.ts +10 -0
  9. package/src/components/MobileToolbar/hooks.ts +68 -0
  10. package/src/components/MobileToolbar/index.ts +1 -0
  11. package/src/components/MobileToolbar/styles.module.scss +104 -0
  12. package/src/components/MobileToolbar/types.ts +53 -0
  13. package/src/components/MobileToolbar/typesUtils.ts +17 -0
  14. package/src/components/index.ts +2 -0
  15. package/src/constants.ts +19 -0
  16. package/src/helperComponents/BulkActions/BulkActions.tsx +95 -0
  17. package/src/helperComponents/BulkActions/constants.ts +4 -0
  18. package/src/helperComponents/BulkActions/index.ts +3 -0
  19. package/src/helperComponents/BulkActions/styles.module.scss +72 -0
  20. package/src/helperComponents/BulkActions/types.ts +31 -0
  21. package/src/helperComponents/BulkActionsCheckbox/BulkActionsCheckbox.tsx +37 -0
  22. package/src/helperComponents/BulkActionsCheckbox/index.ts +1 -0
  23. package/src/helperComponents/BulkActionsCheckbox/styles.module.scss +23 -0
  24. package/src/helperComponents/FilterButton/FilterButton.tsx +28 -0
  25. package/src/helperComponents/FilterButton/index.ts +1 -0
  26. package/src/helperComponents/MoreActions/MoreActions.tsx +57 -0
  27. package/src/helperComponents/MoreActions/constants.ts +6 -0
  28. package/src/helperComponents/MoreActions/index.ts +1 -0
  29. package/src/helperComponents/Separator/Separator.tsx +9 -0
  30. package/src/helperComponents/Separator/index.ts +1 -0
  31. package/src/helperComponents/Separator/styles.module.scss +15 -0
  32. package/src/helperComponents/index.ts +5 -0
  33. package/src/index.ts +1 -0
@@ -0,0 +1,17 @@
1
+ export type IsEqual<A, B> = (<G>() => G extends A ? 1 : 2) extends <G>() => G extends B ? 1 : 2 ? true : false;
2
+
3
+ type Filter<KeyType, ExcludeType> =
4
+ IsEqual<KeyType, ExcludeType> extends true ? never : KeyType extends ExcludeType ? never : KeyType;
5
+
6
+ type Except<ObjectType, KeysType extends keyof ObjectType> = {
7
+ [KeyType in keyof ObjectType as Filter<KeyType, KeysType>]: ObjectType[KeyType];
8
+ };
9
+
10
+ export type RequireAtLeastOne<ObjectType, KeysType extends keyof ObjectType = keyof ObjectType> = {
11
+ [Key in KeysType]-?: Required<Pick<ObjectType, Key>> & Partial<Pick<ObjectType, Exclude<KeysType, Key>>>;
12
+ }[KeysType] &
13
+ Except<ObjectType, KeysType>;
14
+
15
+ export type NeverOrUndefined<T> = {
16
+ [P in keyof T]?: never;
17
+ };
@@ -0,0 +1,2 @@
1
+ export * from './AdaptiveToolbar';
2
+ export * from './MobileToolbar';
@@ -0,0 +1,19 @@
1
+ export const TEST_IDS = {
2
+ main: 'toolbar',
3
+ checkbox: 'toolbar__checkbox',
4
+ confirmAction: 'toolbar__confirm-action',
5
+ rejectAction: 'toolbar__reject-action',
6
+ deleteAction: 'toolbar__delete-action',
7
+ deactivateAction: 'toolbar__deactivate-action',
8
+ disabledAction: 'toolbar__disabled-action',
9
+ bulkActions: 'toolbar__bulk-actions',
10
+ moreBulkActionsButton: 'toolbar__more-bulk-actions-button',
11
+ refreshButton: 'toolbar__refresh-button',
12
+ search: 'toolbar__search',
13
+ filterButton: 'toolbar__filter-button',
14
+ filterRow: 'toolbar__filter-row',
15
+ moreActionsButton: 'toolbar__more-actions-button',
16
+ droplist: 'toolbar__droplist',
17
+ option: 'toolbar__droplist-option',
18
+ after: 'toolbar__after',
19
+ };
@@ -0,0 +1,95 @@
1
+ import { useRef, useState } from 'react';
2
+
3
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
4
+ import { MobileDroplist } from '@cloud-ru/uikit-product-mobile-dropdown';
5
+ import { MobileTooltip } from '@cloud-ru/uikit-product-mobile-tooltip';
6
+ import { ButtonFunction } from '@snack-uikit/button';
7
+ import { Counter } from '@snack-uikit/counter';
8
+ import { useDynamicList } from '@snack-uikit/utils';
9
+
10
+ import { TEST_IDS } from '../../constants';
11
+ import styles from './styles.module.scss';
12
+ import { BulkActionsProps } from './types';
13
+
14
+ export function BulkActions({ actions = [], selectedCount, outline }: BulkActionsProps & { outline?: boolean }) {
15
+ const [isOpen, setIsOpen] = useState(false);
16
+
17
+ const parentContainerRef = useRef<HTMLDivElement>(null);
18
+
19
+ const { t } = useLocale('MobileToolbar');
20
+
21
+ const { visibleItems, hiddenItems } = useDynamicList({
22
+ items: actions,
23
+ parentContainerRef,
24
+ maxVisibleItems: 3,
25
+ });
26
+
27
+ return (
28
+ <div className={styles.bulkActionsContainer} data-outline={outline}>
29
+ <div className={styles.counter}>
30
+ {selectedCount && <Counter value={selectedCount} appearance='primary' color='decor' />}
31
+ </div>
32
+ <div className={styles.bulkActionsWrapper} data-test-id={TEST_IDS.bulkActions} ref={parentContainerRef}>
33
+ <div className={styles.smallSeparator} />
34
+ <div className={styles.bulkActions}>
35
+ {visibleItems.map(({ label, icon: Icon, onClick, disabled, tooltip, 'data-test-id': testId }) => (
36
+ <MobileTooltip
37
+ tip={tooltip}
38
+ key={label}
39
+ open={tooltip ? undefined : false}
40
+ placement='top'
41
+ data-test-id={`${testId}-tooltip`}
42
+ >
43
+ <ButtonFunction
44
+ className={styles.action}
45
+ data-test-id={testId}
46
+ iconPosition='before'
47
+ icon={<Icon />}
48
+ label={label}
49
+ size='m'
50
+ onClick={onClick}
51
+ disabled={disabled}
52
+ />
53
+ </MobileTooltip>
54
+ ))}
55
+ </div>
56
+
57
+ {hiddenItems.length > 0 && (
58
+ <MobileDroplist
59
+ open={isOpen}
60
+ onOpenChange={setIsOpen}
61
+ items={hiddenItems.map(({ label, icon: Icon, onClick, disabled, tooltip, 'data-test-id': testId }) => ({
62
+ id: label,
63
+ content: { option: label },
64
+ beforeContent: <Icon />,
65
+ onClick: () => {
66
+ onClick?.();
67
+ setIsOpen(false);
68
+ },
69
+ disabled,
70
+ itemWrapRender: item => (
71
+ <MobileTooltip
72
+ tip={tooltip}
73
+ open={tooltip ? undefined : false}
74
+ placement='right'
75
+ data-test-id={`${testId}-tooltip`}
76
+ >
77
+ {item}
78
+ </MobileTooltip>
79
+ ),
80
+ 'data-test-id': testId,
81
+ }))}
82
+ >
83
+ <ButtonFunction
84
+ className={styles.moreActionButton}
85
+ size='m'
86
+ appearance='primary'
87
+ label={t('more')}
88
+ data-test-id={TEST_IDS.moreBulkActionsButton}
89
+ />
90
+ </MobileDroplist>
91
+ )}
92
+ </div>
93
+ </div>
94
+ );
95
+ }
@@ -0,0 +1,4 @@
1
+ export const SELECTION_MODE = {
2
+ Single: 'single',
3
+ Multiple: 'multiple',
4
+ } as const;
@@ -0,0 +1,3 @@
1
+ export * from './BulkActions';
2
+ export * from './constants';
3
+ export * from './types';
@@ -0,0 +1,72 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-toolbar' as toolbar;
2
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-element' as element;
3
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as vars;
4
+
5
+ .bulkActionsContainer {
6
+ position: relative;
7
+ display: flex;
8
+ flex-direction: row;
9
+ align-items: center;
10
+ }
11
+
12
+ .counter {
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ flex-shrink: 0;
17
+ flex-grow: 0;
18
+ width: 40px;
19
+ height: 40px;
20
+ }
21
+
22
+ .bulkActionsWrapper {
23
+ @include toolbar.composite-var(toolbar.$toolbar-bulk-action-wrap);
24
+
25
+ width: 100%;
26
+ scrollbar-width: none; /* Firefox */
27
+
28
+ display: flex;
29
+ flex-grow: 1;
30
+ flex-shrink: 1;
31
+ justify-content: start;
32
+ align-items: center;
33
+
34
+ min-width: 0;
35
+
36
+ /* Hide scrollbar */
37
+ -ms-overflow-style: none; /* IE and Edge */
38
+
39
+ &::-webkit-scrollbar {
40
+ display: none; /* Chrome, Safari and Opera */
41
+ }
42
+ }
43
+
44
+ .bulkActions {
45
+ @include toolbar.composite-var(toolbar.$toolbar-bulk-action);
46
+
47
+ display: flex;
48
+ flex-grow: 1;
49
+ flex-shrink: 1;
50
+ padding-left: 8px;
51
+ }
52
+
53
+ .moreActionButton {
54
+ flex-shrink: 0;
55
+ padding-left: 4px;
56
+ }
57
+
58
+ .action {
59
+ white-space: nowrap;
60
+
61
+ &[data-full-width]:not([data-variant="icon-only"]) {
62
+ flex-shrink: 0;
63
+ width: auto;
64
+ }
65
+ }
66
+
67
+ .smallSeparator {
68
+ box-sizing: border-box;
69
+ width: toolbar.$border-width-toolbar-separator;
70
+ height: 16px;
71
+ background-color: toolbar.$sys-neutral-decor-default;
72
+ }
@@ -0,0 +1,31 @@
1
+ import { JSXElementConstructor } from 'react';
2
+
3
+ import { MobileTooltipProps } from '@cloud-ru/uikit-product-mobile-tooltip';
4
+ import { ValueOf, WithSupportProps } from '@snack-uikit/utils';
5
+
6
+ import { SELECTION_MODE } from './constants';
7
+
8
+ export type SelectionMode = ValueOf<typeof SELECTION_MODE>;
9
+
10
+ export type BulkAction = WithSupportProps<{
11
+ label: string;
12
+ icon: JSXElementConstructor<{ className?: string }>;
13
+ disabled?: boolean;
14
+ tooltip?: MobileTooltipProps['tip'];
15
+ onClick?(): void;
16
+ }>;
17
+
18
+ export type BulkActionsProps = {
19
+ /** Список массовых действий */
20
+ actions?: BulkAction[];
21
+ /** Колбек смены значения чекбокса */
22
+ onCheck?(): void;
23
+ /** Значение чекбокса */
24
+ checked?: boolean;
25
+ /** Состояние частичного выбора */
26
+ indeterminate?: boolean;
27
+ /** Режим выбора @default 'multiple'*/
28
+ selectionMode?: SelectionMode;
29
+ /** Количество выбранных элементов */
30
+ selectedCount?: number;
31
+ };
@@ -0,0 +1,37 @@
1
+ import { KeyboardEvent, useCallback } from 'react';
2
+
3
+ import { Checkbox } from '@snack-uikit/toggles';
4
+
5
+ import { TEST_IDS } from '../../constants';
6
+ import styles from './styles.module.scss';
7
+
8
+ export type BulkActionsCheckboxProps = {
9
+ onCheck?: () => void;
10
+ checked?: boolean;
11
+ indeterminate?: boolean;
12
+ };
13
+
14
+ export function BulkActionsCheckbox({ onCheck, checked, indeterminate }: BulkActionsCheckboxProps) {
15
+ const handleKeyDown = useCallback(
16
+ (e: KeyboardEvent<HTMLDivElement>) => {
17
+ if (e.key === ' ' || e.key === 'Enter') {
18
+ onCheck?.();
19
+ }
20
+ },
21
+ [onCheck],
22
+ );
23
+
24
+ return (
25
+ <div
26
+ className={styles.checkboxWrapper}
27
+ onClick={onCheck}
28
+ tabIndex={0}
29
+ role='checkbox'
30
+ aria-checked={checked}
31
+ onKeyDown={handleKeyDown}
32
+ data-test-id={TEST_IDS.checkbox}
33
+ >
34
+ <Checkbox size='s' checked={checked} indeterminate={indeterminate} tabIndex={-1} />
35
+ </div>
36
+ );
37
+ }
@@ -0,0 +1 @@
1
+ export * from './BulkActionsCheckbox';
@@ -0,0 +1,23 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-toolbar' as toolbar;
2
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-element' as element;
3
+
4
+ .checkboxWrapper {
5
+ @include toolbar.composite-var(toolbar.$toolbar-checkbox);
6
+
7
+ cursor: pointer;
8
+
9
+ position: relative;
10
+
11
+ display: flex;
12
+ flex-shrink: 0;
13
+ align-items: center;
14
+ justify-content: center;
15
+
16
+ box-sizing: border-box;
17
+
18
+ &:focus-visible {
19
+ @include element.outline-var(element.$container-focused-s);
20
+
21
+ outline-color: toolbar.$sys-available-complementary;
22
+ }
23
+ }
@@ -0,0 +1,28 @@
1
+ import { FilterSVG } from '@cloud-ru/uikit-product-icons';
2
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
3
+ import { MobileTooltip } from '@cloud-ru/uikit-product-mobile-tooltip';
4
+ import { ButtonFunction } from '@snack-uikit/button';
5
+
6
+ import { TEST_IDS } from '../../constants';
7
+
8
+ export type FilterButtonProps = {
9
+ open: boolean;
10
+ onOpenChange(open: boolean): void;
11
+ numberOfFilters?: number;
12
+ };
13
+
14
+ export function FilterButton({ open, onOpenChange, numberOfFilters }: FilterButtonProps) {
15
+ const { t } = useLocale('Toolbar');
16
+
17
+ return (
18
+ <MobileTooltip tip={open ? t('hideFilters') : t('showFilters')}>
19
+ <ButtonFunction
20
+ size='m'
21
+ icon={<FilterSVG />}
22
+ onClick={() => onOpenChange(!open)}
23
+ counter={numberOfFilters ? { value: numberOfFilters, appearance: 'neutral' } : undefined}
24
+ data-test-id={TEST_IDS.filterButton}
25
+ />
26
+ </MobileTooltip>
27
+ );
28
+ }
@@ -0,0 +1 @@
1
+ export * from './FilterButton';
@@ -0,0 +1,57 @@
1
+ import { ReactNode, useState } from 'react';
2
+
3
+ import { MoreSVG } from '@cloud-ru/uikit-product-icons';
4
+ import { BaseItemProps, MobileDroplist } from '@cloud-ru/uikit-product-mobile-dropdown';
5
+ import { ButtonFunction } from '@snack-uikit/button';
6
+ import { Tag } from '@snack-uikit/tag';
7
+
8
+ import { TEST_IDS } from '../../constants';
9
+
10
+ type Action = {
11
+ tagLabel?: string;
12
+ icon?: ReactNode;
13
+ } & Pick<BaseItemProps, 'content' | 'disabled' | 'onClick'>;
14
+
15
+ export type MoreActionsProps = {
16
+ items?: Action[];
17
+ pinTop?: Action[];
18
+ };
19
+
20
+ export function MoreActions({ items = [], pinTop }: MoreActionsProps) {
21
+ const [isOpen, setIsOpen] = useState<boolean>(false);
22
+
23
+ return (
24
+ <MobileDroplist
25
+ open={isOpen}
26
+ data-test-id={TEST_IDS.droplist}
27
+ onOpenChange={setIsOpen}
28
+ scroll
29
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
30
+ // @ts-ignore
31
+ size='l'
32
+ pinTop={pinTop?.map(item => ({
33
+ ...item,
34
+ beforeContent: item.icon,
35
+ onClick: e => {
36
+ item.onClick?.(e);
37
+ setIsOpen(false);
38
+ e.stopPropagation();
39
+ },
40
+ }))}
41
+ items={items.map(item => ({
42
+ onClick: e => {
43
+ item.onClick?.(e);
44
+ setIsOpen(false);
45
+ e.stopPropagation();
46
+ },
47
+ disabled: item.disabled,
48
+ content: item.content,
49
+ beforeContent: item.icon,
50
+ afterContent: item.tagLabel ? <Tag label={item.tagLabel} /> : undefined,
51
+ 'data-test-id': TEST_IDS.option,
52
+ }))}
53
+ >
54
+ <ButtonFunction icon={<MoreSVG size={24} />} size='m' data-test-id={TEST_IDS.moreActionsButton} />
55
+ </MobileDroplist>
56
+ );
57
+ }
@@ -0,0 +1,6 @@
1
+ export const TRIGGER_OPEN_DROPLIST_KEY_KEYS = [
2
+ ' ', // <- Space key
3
+ 'ArrowDown',
4
+ ];
5
+
6
+ export const TRIGGER_CLOSE_DROPLIST_KEY_CODES = ['Escape', 'Tab'];
@@ -0,0 +1 @@
1
+ export * from './MoreActions';
@@ -0,0 +1,9 @@
1
+ import styles from './styles.module.scss';
2
+
3
+ export function Separator() {
4
+ return (
5
+ <div className={styles.separatorWrapper}>
6
+ <div className={styles.separator}></div>
7
+ </div>
8
+ );
9
+ }
@@ -0,0 +1 @@
1
+ export * from './Separator';
@@ -0,0 +1,15 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-toolbar' as toolbar;
2
+
3
+ .separatorWrapper {
4
+ @include toolbar.composite-var(toolbar.$toolbar-separator-wrap);
5
+
6
+ flex-shrink: 0;
7
+ }
8
+
9
+
10
+ .separator {
11
+ box-sizing: border-box;
12
+ width: toolbar.$border-width-toolbar-separator;
13
+ height: 100%;
14
+ background-color: toolbar.$sys-neutral-decor-default;
15
+ }
@@ -0,0 +1,5 @@
1
+ export * from './BulkActions';
2
+ export * from './FilterButton';
3
+ export * from './MoreActions';
4
+ export * from './Separator';
5
+ export * from './BulkActionsCheckbox';
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './components';