@cloud-ru/uikit-product-page-layout 0.21.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 (42) hide show
  1. package/CHANGELOG.md +1391 -0
  2. package/LICENSE +201 -0
  3. package/README.md +8 -0
  4. package/package.json +58 -0
  5. package/src/components/DefaultSubHeader/DefaultSubHeader.tsx +25 -0
  6. package/src/components/DefaultSubHeader/index.ts +1 -0
  7. package/src/components/DefaultSubHeader/styles.module.scss +17 -0
  8. package/src/components/Headline/Headline.tsx +49 -0
  9. package/src/components/Headline/index.ts +1 -0
  10. package/src/components/Headline/styles.module.scss +54 -0
  11. package/src/components/PageCatalog/PageCatalog.tsx +24 -0
  12. package/src/components/PageCatalog/index.ts +1 -0
  13. package/src/components/PageCatalog/styles.module.scss +18 -0
  14. package/src/components/PageForm/PageForm.tsx +136 -0
  15. package/src/components/PageForm/constants.ts +14 -0
  16. package/src/components/PageForm/hooks.tsx +34 -0
  17. package/src/components/PageForm/index.ts +3 -0
  18. package/src/components/PageForm/styles.module.scss +59 -0
  19. package/src/components/PageForm/types.ts +6 -0
  20. package/src/components/PageLoading/PageLoading.tsx +18 -0
  21. package/src/components/PageLoading/index.ts +1 -0
  22. package/src/components/PageLoading/styles.module.scss +14 -0
  23. package/src/components/PageServices/PageServices.tsx +91 -0
  24. package/src/components/PageServices/index.ts +1 -0
  25. package/src/components/PageServices/styles.module.scss +45 -0
  26. package/src/components/PageSidebar/PageSidebar.tsx +133 -0
  27. package/src/components/PageSidebar/contexts.tsx +30 -0
  28. package/src/components/PageSidebar/helperComponents/SidebarSearch/SidebarSearch.tsx +34 -0
  29. package/src/components/PageSidebar/helperComponents/SidebarSearch/index.ts +1 -0
  30. package/src/components/PageSidebar/helperComponents/SidebarSearch/styles.module.scss +28 -0
  31. package/src/components/PageSidebar/helperComponents/SidebarSearchToggle/SidebarSearchToggle.tsx +23 -0
  32. package/src/components/PageSidebar/helperComponents/SidebarSearchToggle/index.ts +1 -0
  33. package/src/components/PageSidebar/helperComponents/SidebarTitle/SidebarTitle.tsx +32 -0
  34. package/src/components/PageSidebar/helperComponents/SidebarTitle/index.ts +1 -0
  35. package/src/components/PageSidebar/helperComponents/SidebarTitle/styles.module.scss +20 -0
  36. package/src/components/PageSidebar/hooks/useItemsCreator.tsx +138 -0
  37. package/src/components/PageSidebar/hooks/useSearchFilter.tsx +57 -0
  38. package/src/components/PageSidebar/index.ts +2 -0
  39. package/src/components/PageSidebar/styles.module.scss +101 -0
  40. package/src/components/PageSidebar/types.ts +41 -0
  41. package/src/components/index.ts +6 -0
  42. package/src/index.ts +1 -0
@@ -0,0 +1,138 @@
1
+ import { MouseEvent, ReactNode, useMemo } from 'react';
2
+
3
+ import { ChevronLeftSVG } from '@cloud-ru/uikit-product-icons';
4
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
5
+ import { ItemProps } from '@snack-uikit/list';
6
+ import { Tooltip } from '@snack-uikit/tooltip';
7
+
8
+ import { SidebarSearchToggle } from '../helperComponents/SidebarSearchToggle';
9
+ import { SidebarTitle } from '../helperComponents/SidebarTitle';
10
+ import { HeaderProps, SidebarItem } from '../types';
11
+
12
+ const EMPTY_LIST: ItemProps[] = [];
13
+
14
+ export function useTopPinnedContent(
15
+ header?: HeaderProps,
16
+ hasSearch?: boolean,
17
+ ): { title?: ReactNode; pinTop: ItemProps[] } {
18
+ const { t } = useLocale('PageLayout');
19
+
20
+ return useMemo(() => {
21
+ switch (header?.type) {
22
+ case 'title':
23
+ return {
24
+ pinTop: [
25
+ {
26
+ content: <SidebarTitle title={header.label} icon={header.icon} afterContent={header.afterContent} />,
27
+ afterContent: hasSearch ? <SidebarSearchToggle /> : null,
28
+ inactive: true,
29
+ },
30
+ ],
31
+ };
32
+ case 'back':
33
+ return {
34
+ pinTop: [
35
+ {
36
+ content: { option: `${t('PageSidebar.backTo')} ${header.label}` },
37
+ beforeContent: <ChevronLeftSVG />,
38
+ onClick: header.href ? undefined : header.onClick,
39
+ itemWrapRender: header.href
40
+ ? item => (
41
+ <a href={header.href} onClick={header.onClick}>
42
+ {item}
43
+ </a>
44
+ )
45
+ : undefined,
46
+ },
47
+ ],
48
+ };
49
+ default:
50
+ return { pinTop: EMPTY_LIST };
51
+ }
52
+ }, [header, t, hasSearch]);
53
+ }
54
+
55
+ export function useItemsContent(items: SidebarItem[], onSelect?: (id: string | number) => void): ItemProps[] {
56
+ return useMemo(() => {
57
+ const getItemsContent = (items: SidebarItem[], onSelect?: (id: string | number) => void) =>
58
+ items.map(
59
+ ({
60
+ id,
61
+ label,
62
+ beforeContent,
63
+ onClick,
64
+ afterContent,
65
+ disabledReason,
66
+ disabledReasonPlacement,
67
+ ...rest
68
+ }): ItemProps => {
69
+ const href = 'href' in rest ? rest.href : undefined;
70
+ const newItems = 'items' in rest ? rest.items : undefined;
71
+ const type = 'type' in rest ? rest.type : undefined;
72
+
73
+ const clickHandler = (event: MouseEvent<HTMLElement>) => {
74
+ if (href && (event?.metaKey || event?.button === 1)) {
75
+ return;
76
+ }
77
+
78
+ event.preventDefault();
79
+ onClick?.(event);
80
+ onSelect?.(id);
81
+ };
82
+
83
+ if (type === 'group') {
84
+ return {
85
+ ...rest,
86
+ label,
87
+ type: 'group',
88
+ items: getItemsContent(newItems || [], onSelect),
89
+ };
90
+ }
91
+
92
+ const newItem = {
93
+ id,
94
+ content: { option: label },
95
+ itemWrapRender: (item: ReactNode) => {
96
+ if (!disabledReason) {
97
+ return href ? (
98
+ <a href={href} onClick={clickHandler}>
99
+ {item}
100
+ </a>
101
+ ) : (
102
+ item
103
+ );
104
+ }
105
+
106
+ return (
107
+ <Tooltip
108
+ hoverDelayOpen={500}
109
+ open={disabledReason ? undefined : false}
110
+ tip={disabledReason}
111
+ placement={disabledReasonPlacement}
112
+ >
113
+ {item}
114
+ </Tooltip>
115
+ );
116
+ },
117
+
118
+ onClick: href ? undefined : clickHandler,
119
+ beforeContent,
120
+ afterContent,
121
+ disabled: Boolean(disabledReason),
122
+ };
123
+
124
+ if (newItems?.length) {
125
+ return {
126
+ ...newItem,
127
+ type: 'collapse',
128
+ items: getItemsContent(newItems || [], onSelect),
129
+ };
130
+ }
131
+
132
+ return newItem;
133
+ },
134
+ );
135
+
136
+ return getItemsContent(items, onSelect);
137
+ }, [items, onSelect]);
138
+ }
@@ -0,0 +1,57 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { useSearchContext } from '../contexts';
4
+ import { SidebarItem } from '../types';
5
+
6
+ const matchString = (value: string, searchValue: string) =>
7
+ value.trim().toLowerCase().includes(searchValue.trim().toLowerCase());
8
+
9
+ const filterService = (servicesList: SidebarItem[], searchValue: string): SidebarItem[] => {
10
+ if (!searchValue) return servicesList;
11
+
12
+ return servicesList.reduce((result, service) => {
13
+ if (service.label && matchString(service.label, searchValue)) {
14
+ result.push(service);
15
+ return result;
16
+ }
17
+
18
+ if ('items' in service && service.items?.length) {
19
+ const items = filterService(service.items, searchValue);
20
+
21
+ if (items.length) {
22
+ result.push({ ...service, items });
23
+ }
24
+
25
+ return result;
26
+ }
27
+
28
+ return result;
29
+ }, [] as SidebarItem[]);
30
+ };
31
+
32
+ const getSidebarItemIds = (list: SidebarItem[]): (string | number)[] =>
33
+ list.reduce(
34
+ (result, el) => {
35
+ result.push(el.id);
36
+ if ('items' in el && el.items?.length) {
37
+ const childItems = getSidebarItemIds(el.items);
38
+ result.push(...childItems);
39
+ }
40
+ return result;
41
+ },
42
+ [] as (string | number)[],
43
+ );
44
+
45
+ export const useSearchFilter = (list: SidebarItem[]) => {
46
+ const { searchValue, setSearchValue, searchOpened } = useSearchContext();
47
+ const filteredList = useMemo(() => filterService(list, searchValue), [list, searchValue]);
48
+ const searchCollapseState = useMemo(() => ({ value: getSidebarItemIds(filteredList) }), [filteredList]);
49
+
50
+ return {
51
+ searchOpened,
52
+ searchValue,
53
+ setSearchValue,
54
+ filteredList,
55
+ searchCollapseState,
56
+ };
57
+ };
@@ -0,0 +1,2 @@
1
+ export * from './PageSidebar';
2
+ export { type SidebarItem } from './types';
@@ -0,0 +1,101 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables.scss';
2
+
3
+ $width: 256px;
4
+ $catcherWidth: styles-theme-variables.$dimension-050m;
5
+ $instantAnimation: left .15s ease-in-out;
6
+ $delayedAnimation: left .15s .35s ease-in-out;
7
+ $searchMargin: 52px;
8
+
9
+ .wrapper {
10
+ width: $width;
11
+
12
+ &[data-collapsed] {
13
+ width: 0;
14
+ }
15
+
16
+ .body {
17
+ position: absolute;
18
+ height: 100%;
19
+ width: $width;
20
+ display: flex;
21
+ transition: $delayedAnimation;
22
+
23
+
24
+ &[data-hover-off] {
25
+ pointer-events: none;
26
+ transition: $instantAnimation;
27
+ }
28
+
29
+ &[data-collapsed] {
30
+ left: calc(-1 * $width);
31
+
32
+ &::before {
33
+ position: absolute;
34
+ content: ' ';
35
+ height: 100%;
36
+ right: calc(-1 * $catcherWidth);
37
+ width: $catcherWidth;
38
+ // uncomment for debug
39
+ // background-color: aqua;
40
+ // opacity: 0.2;
41
+ }
42
+
43
+ &:hover {
44
+ transition: $instantAnimation;
45
+ left: 0;
46
+
47
+ &::before {
48
+ right: calc(-4 * $catcherWidth);
49
+ width: calc(4 * $catcherWidth);
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ .content {
56
+ flex-grow: 1;
57
+ display: flex;
58
+ overflow: hidden;
59
+ box-sizing: border-box;
60
+ padding: 4px 0;
61
+ flex-direction: column;
62
+ background-color: styles-theme-variables.$sys-neutral-background;
63
+ border-right: 1px solid styles-theme-variables.$sys-neutral-decor-default;
64
+
65
+ &[data-collapsed] {
66
+ box-shadow: styles-theme-variables.$box-shadow-elevation-level3;
67
+ border-right: none;
68
+ margin: 4px;
69
+ }
70
+ }
71
+
72
+ .list {
73
+ flex-grow: 1;
74
+ flex-shrink: 1;
75
+ overflow-y: auto;
76
+ display: flex;
77
+ flex-direction: column-reverse;
78
+ position: relative;
79
+ }
80
+
81
+ .searchWrapper {
82
+ position: absolute;
83
+ right: calc($searchMargin - 10px);
84
+ top: 4px;
85
+ width: calc($width - $searchMargin);
86
+ }
87
+
88
+ .toggler {
89
+ display: flex;
90
+ justify-content: end;
91
+ flex-shrink: 0;
92
+ flex-grow: 0;
93
+ padding: 0 4px;
94
+ }
95
+
96
+ .expandButton {
97
+ position: fixed;
98
+ bottom: 8px;
99
+ left: 8px;
100
+ }
101
+ }
@@ -0,0 +1,41 @@
1
+ import { AnchorHTMLAttributes, MouseEvent, MouseEventHandler, ReactNode } from 'react';
2
+
3
+ import { IconPredefinedProps } from '@snack-uikit/icon-predefined';
4
+ import { TooltipProps } from '@snack-uikit/tooltip';
5
+
6
+ export type SidebarItemBase = {
7
+ id: string | number;
8
+ label: string;
9
+ 'data-test-id'?: string;
10
+ onClick?(e: MouseEvent<HTMLElement>): void;
11
+ afterContent?: ReactNode;
12
+ disabledReason?: ReactNode;
13
+ disabledReasonPlacement?: TooltipProps['placement'];
14
+ beforeContent?: ReactNode;
15
+ };
16
+
17
+ export type SidebarItemWithHref = SidebarItemBase & {
18
+ href: string;
19
+ };
20
+
21
+ export type SidebarItemWithItems = SidebarItemBase & {
22
+ /** @default collapse */
23
+ type?: 'collapse' | 'group';
24
+ divider?: boolean;
25
+ // eslint-disable-next-line no-use-before-define
26
+ items?: SidebarItem[];
27
+ };
28
+
29
+ export type SidebarItem = SidebarItemWithHref | SidebarItemWithItems;
30
+
31
+ export type Icon = IconPredefinedProps['icon'];
32
+
33
+ export type HeaderProps =
34
+ | { type: 'title'; label: string; icon: Icon; afterContent?: ReactNode }
35
+ | { type: 'back'; label: string; href?: string; onClick?: MouseEventHandler };
36
+
37
+ export type Documentation = {
38
+ href?: string;
39
+ onClick?: MouseEventHandler;
40
+ target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
41
+ };
@@ -0,0 +1,6 @@
1
+ export * from './PageCatalog';
2
+ export * from './PageForm';
3
+ export * from './PageLoading';
4
+ export * from './PageServices';
5
+ export * from './DefaultSubHeader';
6
+ export * from './PageSidebar';
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './components';