@cloud-ru/ds-popup-private 1.0.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 (112) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +201 -0
  3. package/README.md +163 -0
  4. package/dist/cjs/components/FooterActions/FooterActions.d.ts +44 -0
  5. package/dist/cjs/components/FooterActions/FooterActions.js +44 -0
  6. package/dist/cjs/components/FooterActions/index.d.ts +1 -0
  7. package/dist/cjs/components/FooterActions/index.js +17 -0
  8. package/dist/cjs/components/FooterActions/styles.module.css +20 -0
  9. package/dist/cjs/components/PopupBody/PopupBody.d.ts +3 -0
  10. package/dist/cjs/components/PopupBody/PopupBody.js +15 -0
  11. package/dist/cjs/components/PopupBody/index.d.ts +1 -0
  12. package/dist/cjs/components/PopupBody/index.js +17 -0
  13. package/dist/cjs/components/PopupBody/styles.module.css +16 -0
  14. package/dist/cjs/components/PopupCloseButton/PopupCloseButton.d.ts +11 -0
  15. package/dist/cjs/components/PopupCloseButton/PopupCloseButton.js +16 -0
  16. package/dist/cjs/components/PopupCloseButton/index.d.ts +1 -0
  17. package/dist/cjs/components/PopupCloseButton/index.js +17 -0
  18. package/dist/cjs/components/PopupCloseButton/styles.module.css +109 -0
  19. package/dist/cjs/components/PopupFooter/PopupFooter.d.ts +3 -0
  20. package/dist/cjs/components/PopupFooter/PopupFooter.js +17 -0
  21. package/dist/cjs/components/PopupFooter/index.d.ts +1 -0
  22. package/dist/cjs/components/PopupFooter/index.js +17 -0
  23. package/dist/cjs/components/PopupFooter/styles.module.css +32 -0
  24. package/dist/cjs/components/PopupHeader/PopupHeader.d.ts +3 -0
  25. package/dist/cjs/components/PopupHeader/PopupHeader.js +37 -0
  26. package/dist/cjs/components/PopupHeader/index.d.ts +1 -0
  27. package/dist/cjs/components/PopupHeader/index.js +17 -0
  28. package/dist/cjs/components/PopupHeader/styles.module.css +128 -0
  29. package/dist/cjs/components/PopupMedia/PopupMedia.d.ts +7 -0
  30. package/dist/cjs/components/PopupMedia/PopupMedia.js +14 -0
  31. package/dist/cjs/components/PopupMedia/index.d.ts +1 -0
  32. package/dist/cjs/components/PopupMedia/index.js +17 -0
  33. package/dist/cjs/components/PopupMedia/styles.module.css +27 -0
  34. package/dist/cjs/components/index.d.ts +6 -0
  35. package/dist/cjs/components/index.js +22 -0
  36. package/dist/cjs/constants.d.ts +68 -0
  37. package/dist/cjs/constants.js +73 -0
  38. package/dist/cjs/context/overlaySurface.d.ts +35 -0
  39. package/dist/cjs/context/overlaySurface.js +45 -0
  40. package/dist/cjs/index.d.ts +4 -0
  41. package/dist/cjs/index.js +20 -0
  42. package/dist/cjs/package.json +3 -0
  43. package/dist/cjs/style.css +332 -0
  44. package/dist/cjs/types.d.ts +95 -0
  45. package/dist/cjs/types.js +2 -0
  46. package/dist/esm/components/FooterActions/FooterActions.d.ts +44 -0
  47. package/dist/esm/components/FooterActions/FooterActions.js +38 -0
  48. package/dist/esm/components/FooterActions/index.d.ts +1 -0
  49. package/dist/esm/components/FooterActions/index.js +1 -0
  50. package/dist/esm/components/FooterActions/styles.module.css +20 -0
  51. package/dist/esm/components/PopupBody/PopupBody.d.ts +3 -0
  52. package/dist/esm/components/PopupBody/PopupBody.js +9 -0
  53. package/dist/esm/components/PopupBody/index.d.ts +1 -0
  54. package/dist/esm/components/PopupBody/index.js +1 -0
  55. package/dist/esm/components/PopupBody/styles.module.css +16 -0
  56. package/dist/esm/components/PopupCloseButton/PopupCloseButton.d.ts +11 -0
  57. package/dist/esm/components/PopupCloseButton/PopupCloseButton.js +10 -0
  58. package/dist/esm/components/PopupCloseButton/index.d.ts +1 -0
  59. package/dist/esm/components/PopupCloseButton/index.js +1 -0
  60. package/dist/esm/components/PopupCloseButton/styles.module.css +109 -0
  61. package/dist/esm/components/PopupFooter/PopupFooter.d.ts +3 -0
  62. package/dist/esm/components/PopupFooter/PopupFooter.js +11 -0
  63. package/dist/esm/components/PopupFooter/index.d.ts +1 -0
  64. package/dist/esm/components/PopupFooter/index.js +1 -0
  65. package/dist/esm/components/PopupFooter/styles.module.css +32 -0
  66. package/dist/esm/components/PopupHeader/PopupHeader.d.ts +3 -0
  67. package/dist/esm/components/PopupHeader/PopupHeader.js +31 -0
  68. package/dist/esm/components/PopupHeader/index.d.ts +1 -0
  69. package/dist/esm/components/PopupHeader/index.js +1 -0
  70. package/dist/esm/components/PopupHeader/styles.module.css +128 -0
  71. package/dist/esm/components/PopupMedia/PopupMedia.d.ts +7 -0
  72. package/dist/esm/components/PopupMedia/PopupMedia.js +8 -0
  73. package/dist/esm/components/PopupMedia/index.d.ts +1 -0
  74. package/dist/esm/components/PopupMedia/index.js +1 -0
  75. package/dist/esm/components/PopupMedia/styles.module.css +27 -0
  76. package/dist/esm/components/index.d.ts +6 -0
  77. package/dist/esm/components/index.js +6 -0
  78. package/dist/esm/constants.d.ts +68 -0
  79. package/dist/esm/constants.js +70 -0
  80. package/dist/esm/context/overlaySurface.d.ts +35 -0
  81. package/dist/esm/context/overlaySurface.js +38 -0
  82. package/dist/esm/index.d.ts +4 -0
  83. package/dist/esm/index.js +4 -0
  84. package/dist/esm/style.css +332 -0
  85. package/dist/esm/types.d.ts +95 -0
  86. package/dist/esm/types.js +1 -0
  87. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  88. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  89. package/package.json +46 -0
  90. package/src/components/FooterActions/FooterActions.tsx +111 -0
  91. package/src/components/FooterActions/index.ts +1 -0
  92. package/src/components/FooterActions/styles.module.scss +32 -0
  93. package/src/components/PopupBody/PopupBody.tsx +20 -0
  94. package/src/components/PopupBody/index.ts +1 -0
  95. package/src/components/PopupBody/styles.module.scss +24 -0
  96. package/src/components/PopupCloseButton/PopupCloseButton.tsx +37 -0
  97. package/src/components/PopupCloseButton/index.ts +1 -0
  98. package/src/components/PopupCloseButton/styles.module.scss +39 -0
  99. package/src/components/PopupFooter/PopupFooter.tsx +24 -0
  100. package/src/components/PopupFooter/index.ts +1 -0
  101. package/src/components/PopupFooter/styles.module.scss +49 -0
  102. package/src/components/PopupHeader/PopupHeader.tsx +125 -0
  103. package/src/components/PopupHeader/index.ts +1 -0
  104. package/src/components/PopupHeader/styles.module.scss +158 -0
  105. package/src/components/PopupMedia/PopupMedia.tsx +18 -0
  106. package/src/components/PopupMedia/index.ts +1 -0
  107. package/src/components/PopupMedia/styles.module.scss +33 -0
  108. package/src/components/index.ts +6 -0
  109. package/src/constants.ts +72 -0
  110. package/src/context/overlaySurface.tsx +65 -0
  111. package/src/index.ts +4 -0
  112. package/src/types.ts +110 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Тип media-блока в overlay-шапке.
3
+ *
4
+ * - `Image` — изображение во всю ширину (высота 184px), прижато к шапке (убирается
5
+ * верхний отступ контент-блока). Горизонтальные паддинги body этим не меняются — они отдельно через `bodyPadding`.
6
+ * - `Icon` — иконка с `padding-top: 24px`.
7
+ *
8
+ * Соответствие Figma: `bottomSheetMediaImage` (360×184) и `bottomSheetMediaIcon` (360×104).
9
+ */
10
+ export const MEDIA_KIND = {
11
+ Image: 'image',
12
+ Icon: 'icon',
13
+ };
14
+ /**
15
+ * Ориентация кнопок футера, собранных из `approveButton` / `cancelButton` / `additionalButton`.
16
+ * Применяется только при ровно двух кнопках (см. `footerActionsOrientation`).
17
+ *
18
+ * - `Horizontal` — кнопки в ряд через space-between: secondary слева, primary справа, ширина по
19
+ * контенту. Дефолт — точное соответствие Figma `bottomBar.buttonGroup` (cancel/confirm).
20
+ * - `Vertical` — кнопки в столбик, full-width (primary сверху).
21
+ */
22
+ export const FOOTER_ACTIONS_ORIENTATION = {
23
+ Vertical: 'vertical',
24
+ Horizontal: 'horizontal',
25
+ };
26
+ /**
27
+ * Стабильные `data-test-id` для слотов overlay-chrome (header / body / footer / media / close).
28
+ *
29
+ * Используются для e2e-локаторов в Playwright и Storybook play-функциях. Реэкспортируется из
30
+ * `@cloud-ru/ds-bottom-sheet`, чтобы потребитель (приложение) ссылался на те же id, что и наши тесты.
31
+ *
32
+ * Значения намеренно сохраняют префикс `bottom-sheet__` — на фазе выноса слоя из `@cloud-ru/ds-bottom-sheet`
33
+ * они переносятся 1:1 без смены поведения; переименование в `popup-private__*` — отдельная фаза.
34
+ *
35
+ * Figma-typo-мост (см. `.claude/rules/figma-integration.md`):
36
+ * Figma `badgeHandle` → код `handle` (показывается при `swipeEnabled`)
37
+ * Figma `blackout` → код `backdrop` / `showBackdrop`
38
+ * Figma `topBar` → код `header` / `onBackButtonClick`
39
+ * Figma `bottomBar` → код `footer`
40
+ * Figma `+ slotMedia` → код `media`
41
+ * Figma `+ slotSubtitle` → код `subtitle` (текст под title)
42
+ * Figma `+ slotSecondTitle` → код `slotSecondTitle` (search/segment, только sheet)
43
+ * Figma `+ slotAfterHeadline` → код `slotAfterTitle`
44
+ * Figma `+ slotBottomBar` → код `footer` (ReactNode)
45
+ * Figma `buttonBefore` (48×48) → код auto-back-button по `onBackButtonClick`
46
+ * Figma `buttonAfter` (48×48) → код `actionButton` (ReactNode)
47
+ */
48
+ export const TEST_IDS = {
49
+ root: 'bottom-sheet',
50
+ backdrop: 'bottom-sheet__backdrop',
51
+ handle: 'bottom-sheet__handle',
52
+ header: 'bottom-sheet__header',
53
+ title: 'bottom-sheet__title',
54
+ slotAfterTitle: 'bottom-sheet__slot-after-title',
55
+ subtitle: 'bottom-sheet__subtitle',
56
+ slotSecondTitle: 'bottom-sheet__slot-second-title',
57
+ backButton: 'bottom-sheet__back-button',
58
+ actionButton: 'bottom-sheet__action-button',
59
+ media: 'bottom-sheet__media',
60
+ closeButton: 'popup__close-button',
61
+ body: 'bottom-sheet__body',
62
+ footer: 'bottom-sheet__footer',
63
+ footerApprove: 'bottom-sheet__footer-approve',
64
+ footerCancel: 'bottom-sheet__footer-cancel',
65
+ footerAdditional: 'bottom-sheet__footer-additional',
66
+ // Два разных разделителя (topBar↔body и body↔footer) — разные id, чтобы каждый адресовался
67
+ // однозначно (без strict-mode-коллизии `getByTestId`).
68
+ dividerTop: 'bottom-sheet__divider-top',
69
+ dividerBottom: 'bottom-sheet__divider-bottom',
70
+ };
@@ -0,0 +1,35 @@
1
+ import { ValueOf } from '@cloud-ru/ds-utils';
2
+ import { ReactNode } from 'react';
3
+ /** Поверхность overlay'я, в которой рендерятся общие слоты (`Header` / `Body` / `Footer` / `Media`). */
4
+ export declare const OVERLAY_SURFACE: {
5
+ readonly Modal: "modal";
6
+ readonly Drawer: "drawer";
7
+ readonly Sheet: "sheet";
8
+ };
9
+ export type OverlaySurface = ValueOf<typeof OVERLAY_SURFACE>;
10
+ /**
11
+ * Сегмент коллекции `popupPrivate.anatomy.*`, из которого слоты берут токены раскладки.
12
+ * Modal и Drawer(desktop) рисуются мастером `window`; Sheet — `bottomSheet`.
13
+ */
14
+ export declare const SURFACE_TOKEN_SEGMENT: {
15
+ readonly modal: "window";
16
+ readonly drawer: "window";
17
+ readonly sheet: "bottomSheet";
18
+ };
19
+ export type SurfaceTokenSegment = ValueOf<typeof SURFACE_TOKEN_SEGMENT>;
20
+ type OverlaySurfaceProviderProps = {
21
+ /** Поверхность, в которой рендерятся дочерние слоты. */
22
+ surface: OverlaySurface;
23
+ /** Только drawer: высота панели по контенту → `Body` (drawer-ветка) добавляет соответствующий класс. */
24
+ bodyHeightAuto?: boolean;
25
+ children: ReactNode;
26
+ };
27
+ /** Провайдер поверхности overlay'я. Frame (sheet / modal / drawer) оборачивает в него свои слоты. */
28
+ export declare function OverlaySurfaceProvider({ surface, bodyHeightAuto, children }: OverlaySurfaceProviderProps): import("react/jsx-runtime").JSX.Element;
29
+ /** Текущая поверхность overlay'я. Без провайдера возвращает `'sheet'`. */
30
+ export declare function useOverlaySurface(): OverlaySurface;
31
+ /** Только drawer-ветка `Body`: высота панели по контенту (из `DrawerFrame`). */
32
+ export declare function useOverlayBodyHeightAuto(): boolean;
33
+ /** Токен-сегмент текущей поверхности (`window` / `bottomSheet`). Без провайдера — `bottomSheet`. */
34
+ export declare function useSurfaceTokenSegment(): SurfaceTokenSegment;
35
+ export {};
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ /** Поверхность overlay'я, в которой рендерятся общие слоты (`Header` / `Body` / `Footer` / `Media`). */
4
+ export const OVERLAY_SURFACE = {
5
+ Modal: 'modal',
6
+ Drawer: 'drawer',
7
+ Sheet: 'sheet',
8
+ };
9
+ /**
10
+ * Сегмент коллекции `popupPrivate.anatomy.*`, из которого слоты берут токены раскладки.
11
+ * Modal и Drawer(desktop) рисуются мастером `window`; Sheet — `bottomSheet`.
12
+ */
13
+ export const SURFACE_TOKEN_SEGMENT = {
14
+ [OVERLAY_SURFACE.Modal]: 'window',
15
+ [OVERLAY_SURFACE.Drawer]: 'window',
16
+ [OVERLAY_SURFACE.Sheet]: 'bottomSheet',
17
+ };
18
+ // Дефолт `'sheet'`: слоты без провайдера ведут себя как bottom-sheet.
19
+ const OverlaySurfaceContext = createContext({
20
+ surface: OVERLAY_SURFACE.Sheet,
21
+ bodyHeightAuto: false,
22
+ });
23
+ /** Провайдер поверхности overlay'я. Frame (sheet / modal / drawer) оборачивает в него свои слоты. */
24
+ export function OverlaySurfaceProvider({ surface, bodyHeightAuto = false, children }) {
25
+ return (_jsx(OverlaySurfaceContext.Provider, { value: { surface, bodyHeightAuto }, children: children }));
26
+ }
27
+ /** Текущая поверхность overlay'я. Без провайдера возвращает `'sheet'`. */
28
+ export function useOverlaySurface() {
29
+ return useContext(OverlaySurfaceContext).surface;
30
+ }
31
+ /** Только drawer-ветка `Body`: высота панели по контенту (из `DrawerFrame`). */
32
+ export function useOverlayBodyHeightAuto() {
33
+ return useContext(OverlaySurfaceContext).bodyHeightAuto;
34
+ }
35
+ /** Токен-сегмент текущей поверхности (`window` / `bottomSheet`). Без провайдера — `bottomSheet`. */
36
+ export function useSurfaceTokenSegment() {
37
+ return SURFACE_TOKEN_SEGMENT[useOverlaySurface()];
38
+ }
@@ -0,0 +1,4 @@
1
+ export * from './components/index.js';
2
+ export * from './constants.js';
3
+ export * from './context/overlaySurface.js';
4
+ export * from './types.js';
@@ -0,0 +1,4 @@
1
+ export * from './components/index.js';
2
+ export * from './constants.js';
3
+ export * from './context/overlaySurface.js';
4
+ export * from './types.js';
@@ -0,0 +1,332 @@
1
+ .actions{
2
+ width:100%;
3
+ }
4
+ .actions > *{
5
+ max-width:100%;
6
+ }
7
+
8
+ .single{
9
+ justify-content:flex-end;
10
+ }
11
+
12
+ .compact{
13
+ align-self:flex-end;
14
+ justify-content:flex-end;
15
+ width:auto;
16
+ }
17
+
18
+ .actions.stackInverted{
19
+ flex-direction:column;
20
+ }
21
+ .root{
22
+ touch-action:pan-y;
23
+ overflow-y:auto;
24
+ overscroll-behavior:contain;
25
+ flex:1 1 auto;
26
+ box-sizing:border-box;
27
+ min-height:0;
28
+ padding:var(--sn-bottomSheet-anatomy-container-content-bodyWrapper-paddingTop, var(--sn-density-spacing-interval-m, var(--sn-primitive-dimension-16, 16px))) var(--sn-bottomSheet-anatomy-container-content-bodyWrapper-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px))) var(--sn-bottomSheet-anatomy-container-content-bodyWrapper-paddingBottom, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px))) var(--sn-bottomSheet-anatomy-container-content-bodyWrapper-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
29
+ color:var(--sn-theme-color-neutral-text, var(--sn-brand-color-neutral-45, var(--sn-primitive-color-gray-45, #81869c)));
30
+ }
31
+
32
+ .root[data-no-padding]{
33
+ padding-top:0;
34
+ padding-right:0;
35
+ padding-left:0;
36
+ }
37
+ @charset "UTF-8";
38
+ .stateLayer{
39
+ visibility:initial;
40
+ }
41
+
42
+ .root{
43
+ cursor:pointer;
44
+ position:relative;
45
+ display:flex;
46
+ align-items:center;
47
+ justify-content:center;
48
+ box-sizing:border-box;
49
+ margin:var(--sn-popupPrivate-anatomy-window-badgeButtonClosed-paddingTop, var(--sn-brand-anatomy-spacing-window-badgeButtonClosed-paddingTop, var(--sn-primitive-dimension-48, 48px))) var(--sn-popupPrivate-anatomy-window-badgeButtonClosed-paddingRight, var(--sn-brand-anatomy-spacing-window-badgeButtonClosed-paddingRight, var(--sn-primitive-dimension-48, 48px))) var(--sn-popupPrivate-anatomy-window-badgeButtonClosed-paddingBottom, var(--sn-brand-anatomy-spacing-window-badgeButtonClosed-paddingBottom, var(--sn-primitive-dimension-0, 0px))) var(--sn-popupPrivate-anatomy-window-badgeButtonClosed-paddingLeft, var(--sn-brand-anatomy-spacing-window-badgeButtonClosed-paddingLeft, var(--sn-primitive-dimension-0, 0px)));
50
+ padding:0;
51
+ border:0 solid transparent;
52
+ color:var(--sn-theme-color-neutral-text, var(--sn-brand-color-neutral-45, var(--sn-primitive-color-gray-45, #81869c)));
53
+ background:transparent;
54
+ outline:0;
55
+ min-width:var(--sn-popupPrivate-anatomy-window-badgeButtonClosed-buttonClosed-minWidth, var(--sn-primitive-dimension-32, 32px));
56
+ min-height:var(--sn-popupPrivate-anatomy-window-badgeButtonClosed-buttonClosed-minHeight, var(--sn-primitive-dimension-32, 32px));
57
+ border-radius:var(--sn-popupPrivate-anatomy-window-badgeButtonClosed-buttonClosed-borderRadius, var(--sn-brand-anatomy-radius-window-buttonClosed-m, var(--sn-primitive-dimension-full, 10000px)));
58
+ }
59
+ .root .stateLayer{
60
+ position:absolute;
61
+ inset:0;
62
+ border-radius:inherit;
63
+ }
64
+ .root .stateLayer[data-state=borderOnBackground]{
65
+ border-color:var(--sn-theme-color-material-stateLayer-neutral-border-default-borderColor, var(--sn-brand-color-state-neutralBorder-forLightMode-default-borderColor, rgba(159, 166, 198, 0.3019607843)));
66
+ border-style:solid;
67
+ border-width:var(--sn-primitive-strokeWeight-strokeRegular, 1px);
68
+ }
69
+ .root .stateLayer[data-state=emptyNeutralOnBackground]{
70
+ background:var(--sn-theme-color-material-stateLayer-neutral-onBackground-defaultEmpty-filled, var(--sn-brand-color-state-neutral-forLightMode-default-background, rgba(120, 123, 138, 0)));
71
+ }
72
+ .root .stateLayer[data-state=activatedOnBackground]{
73
+ background:var(--sn-theme-color-material-stateLayer-activated-default-filled, var(--sn-brand-color-state-activated-default-background, var(--sn-brandRole-color-brandA-backgroundActivatedDefault, rgba(56, 159, 116, 0.1490196078))));
74
+ }
75
+ .root .stateLayer[data-state=versionOnColor]{
76
+ background:var(--sn-theme-color-material-stateLayer-versionOnColor-default-filled, rgba(0, 0, 0, 0.0509803922));
77
+ }
78
+ .root .stateLayer[data-state=emptyVersionOnColor]{
79
+ background:var(--sn-theme-color-material-stateLayer-versionOnColor-defaultEmpty-filled, rgba(0, 0, 0, 0));
80
+ }
81
+ .root .stateLayer[data-state=inversionOnColor]{
82
+ background:var(--sn-theme-color-material-stateLayer-inversionOnColor-default-filled, rgba(255, 255, 255, 0.1019607843));
83
+ }
84
+ .root .stateLayer[data-state=emptyInversionOnColor]{
85
+ background:var(--sn-theme-color-material-stateLayer-inversionOnColor-defaultEmpty-filled, rgba(255, 255, 255, 0));
86
+ }
87
+ .root .stateLayer[data-state=emptyDarkOnAccent]{
88
+ background:var(--sn-theme-color-material-stateLayer-darkOnAccent-defaultEmpty-filled, rgba(0, 0, 0, 0));
89
+ }
90
+ @media (hover: hover){
91
+ .root:hover:not([data-disabled], [data-loading]) .stateLayer[data-state=borderOnBackground]{
92
+ border-color:var(--sn-theme-color-material-stateLayer-neutral-border-hovered-borderColor, var(--sn-brand-color-state-neutralBorder-forLightMode-hovered-borderColor, rgba(159, 166, 198, 0.6509803922)));
93
+ }
94
+ .root:hover:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyNeutralOnBackground]{
95
+ background:var(--sn-theme-color-material-stateLayer-neutral-onBackground-hovered-filled, var(--sn-brand-color-state-neutral-forLightMode-hovered-background, rgba(188, 195, 220, 0.1490196078)));
96
+ }
97
+ .root:hover:not([data-disabled], [data-loading]) .stateLayer[data-state=activatedOnBackground]{
98
+ background:var(--sn-theme-color-material-stateLayer-activated-hovered-filled, var(--sn-brand-color-state-activated-hovered-background, var(--sn-brandRole-color-brandA-backgroundActivatedHovered, rgba(56, 159, 116, 0.3490196078))));
99
+ }
100
+ .root:hover:not([data-disabled], [data-loading]) .stateLayer[data-state=versionOnColor]{
101
+ background:var(--sn-theme-color-material-stateLayer-versionOnColor-hovered-filled, rgba(0, 0, 0, 0.1019607843));
102
+ }
103
+ .root:hover:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyVersionOnColor]{
104
+ background:var(--sn-theme-color-material-stateLayer-versionOnColor-hovered-filled, rgba(0, 0, 0, 0.1019607843));
105
+ }
106
+ .root:hover:not([data-disabled], [data-loading]) .stateLayer[data-state=inversionOnColor]{
107
+ background:var(--sn-theme-color-material-stateLayer-inversionOnColor-hovered-filled, rgba(255, 255, 255, 0.1490196078));
108
+ }
109
+ .root:hover:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyInversionOnColor]{
110
+ background:var(--sn-theme-color-material-stateLayer-inversionOnColor-hovered-filled, rgba(255, 255, 255, 0.1490196078));
111
+ }
112
+ .root:hover:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyDarkOnAccent]{
113
+ background:var(--sn-theme-color-material-stateLayer-darkOnAccent-hovered-filled, rgba(0, 0, 0, 0.0588235294));
114
+ }
115
+ }
116
+ .root{
117
+ }
118
+ .root[data-loading] .stateLayer[data-state=borderOnBackground], .root:active:not([data-disabled], [data-loading]) .stateLayer[data-state=borderOnBackground], .root[data-pressed=true]:not([data-disabled], [data-loading]) .stateLayer[data-state=borderOnBackground]{
119
+ border-color:var(--sn-theme-color-material-stateLayer-neutral-border-pressed-borderColor, var(--sn-brand-color-state-neutralBorder-forLightMode-pressed-borderColor, rgba(159, 166, 198, 0.8)));
120
+ }
121
+ .root[data-loading] .stateLayer[data-state=emptyNeutralOnBackground], .root:active:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyNeutralOnBackground], .root[data-pressed=true]:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyNeutralOnBackground]{
122
+ background:var(--sn-theme-color-material-stateLayer-neutral-onBackground-pressed-filled, var(--sn-brand-color-state-neutral-forLightMode-pressed-background, rgba(188, 195, 220, 0.3019607843)));
123
+ }
124
+ .root[data-loading] .stateLayer[data-state=activatedOnBackground], .root:active:not([data-disabled], [data-loading]) .stateLayer[data-state=activatedOnBackground], .root[data-pressed=true]:not([data-disabled], [data-loading]) .stateLayer[data-state=activatedOnBackground]{
125
+ background:var(--sn-theme-color-material-stateLayer-activated-pressed-filled, var(--sn-brand-color-state-activated-pressed-background, var(--sn-brandRole-color-brandA-backgroundActivatedPressed, rgba(56, 159, 116, 0.4509803922))));
126
+ }
127
+ .root[data-loading] .stateLayer[data-state=versionOnColor], .root:active:not([data-disabled], [data-loading]) .stateLayer[data-state=versionOnColor], .root[data-pressed=true]:not([data-disabled], [data-loading]) .stateLayer[data-state=versionOnColor]{
128
+ background:var(--sn-theme-color-material-stateLayer-versionOnColor-pressed-filled, rgba(0, 0, 0, 0.1490196078));
129
+ }
130
+ .root[data-loading] .stateLayer[data-state=emptyVersionOnColor], .root:active:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyVersionOnColor], .root[data-pressed=true]:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyVersionOnColor]{
131
+ background:var(--sn-theme-color-material-stateLayer-versionOnColor-pressed-filled, rgba(0, 0, 0, 0.1490196078));
132
+ }
133
+ .root[data-loading] .stateLayer[data-state=inversionOnColor], .root:active:not([data-disabled], [data-loading]) .stateLayer[data-state=inversionOnColor], .root[data-pressed=true]:not([data-disabled], [data-loading]) .stateLayer[data-state=inversionOnColor]{
134
+ background:var(--sn-theme-color-material-stateLayer-inversionOnColor-pressed-filled, rgba(255, 255, 255, 0.0509803922));
135
+ }
136
+ .root[data-loading] .stateLayer[data-state=emptyInversionOnColor], .root:active:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyInversionOnColor], .root[data-pressed=true]:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyInversionOnColor]{
137
+ background:var(--sn-theme-color-material-stateLayer-inversionOnColor-pressed-filled, rgba(255, 255, 255, 0.0509803922));
138
+ }
139
+ .root[data-loading] .stateLayer[data-state=emptyDarkOnAccent], .root:active:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyDarkOnAccent], .root[data-pressed=true]:not([data-disabled], [data-loading]) .stateLayer[data-state=emptyDarkOnAccent]{
140
+ background:var(--sn-theme-color-material-stateLayer-darkOnAccent-pressed-filled, rgba(0, 0, 0, 0.1215686275));
141
+ }
142
+ .root:focus-visible{
143
+ outline:var(--sn-theme-color-available-complementary, #000000) solid var(--sn-primitive-strokeWeight-strokeSemiBold, 2px);
144
+ outline-offset:var(--sn-primitive-strokeWeight-strokeSemiBold, 2px);
145
+ }
146
+ .root{
147
+ display:flex;
148
+ flex-shrink:0;
149
+ box-sizing:border-box;
150
+ }
151
+ .root[data-surface=bottomSheet]{
152
+ touch-action:none;
153
+ flex-direction:column;
154
+ align-items:stretch;
155
+ justify-content:center;
156
+ padding:var(--sn-popupPrivate-anatomy-bottomSheet-footer-paddingTop, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px))) var(--sn-popupPrivate-anatomy-bottomSheet-footer-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px))) var(--sn-popupPrivate-anatomy-bottomSheet-footer-paddingBottom, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px))) var(--sn-popupPrivate-anatomy-bottomSheet-footer-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
157
+ }
158
+ .root[data-surface=window]{
159
+ align-items:center;
160
+ align-self:stretch;
161
+ padding-left:var(--sn-popupPrivate-anatomy-window-footer-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
162
+ padding-right:var(--sn-popupPrivate-anatomy-window-footer-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
163
+ padding-bottom:var(--sn-popupPrivate-anatomy-window-footer-paddingBottom, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
164
+ }
165
+
166
+ .contentWrapper{
167
+ display:flex;
168
+ flex-direction:column;
169
+ }
170
+ [data-surface=bottomSheet] .contentWrapper{
171
+ gap:var(--sn-density-spacing-interval-m, var(--sn-primitive-dimension-16, 16px));
172
+ }
173
+ [data-surface=window] .contentWrapper{
174
+ flex:1 0 0;
175
+ align-items:flex-start;
176
+ justify-content:center;
177
+ }
178
+ .root{
179
+ display:flex;
180
+ flex-direction:column;
181
+ flex-shrink:0;
182
+ box-sizing:border-box;
183
+ }
184
+ .root[data-surface=bottomSheet]{
185
+ gap:var(--sn-popupPrivate-anatomy-bottomSheet-header-gap, var(--sn-density-spacing-interval-xs, var(--sn-primitive-dimension-4, 4px)));
186
+ padding-top:var(--sn-popupPrivate-anatomy-bottomSheet-header-paddingTop, var(--sn-density-spacing-interval-m, var(--sn-primitive-dimension-16, 16px)));
187
+ padding-bottom:var(--sn-popupPrivate-anatomy-bottomSheet-header-paddingBottom, var(--sn-density-spacing-interval-m, var(--sn-primitive-dimension-16, 16px)));
188
+ }
189
+ .root[data-surface=window]{
190
+ align-items:flex-start;
191
+ align-self:stretch;
192
+ justify-content:center;
193
+ gap:var(--sn-popupPrivate-anatomy-window-header-gap, var(--sn-density-spacing-interval-xs, var(--sn-primitive-dimension-4, 4px)));
194
+ padding-bottom:var(--sn-popupPrivate-anatomy-window-header-paddingBottom, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
195
+ }
196
+ .root[data-surface=window]:not([data-with-before]){
197
+ gap:0;
198
+ }
199
+
200
+ .headlineWrapper{
201
+ display:flex;
202
+ align-items:center;
203
+ }
204
+ [data-surface=bottomSheet] .headlineWrapper{
205
+ touch-action:none;
206
+ gap:0;
207
+ }
208
+ [data-surface=bottomSheet] .headlineWrapper[data-with-before], [data-surface=bottomSheet] .headlineWrapper[data-with-after]{
209
+ gap:var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-gap, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
210
+ }
211
+ [data-surface=bottomSheet] .headlineWrapper[data-with-before]{
212
+ padding-left:var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-paddingLeft, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
213
+ }
214
+ [data-surface=bottomSheet] .headlineWrapper[data-with-after]{
215
+ padding-right:var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-paddingRight, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
216
+ }
217
+ [data-surface=window] .headlineWrapper{
218
+ align-self:stretch;
219
+ gap:var(--sn-popupPrivate-anatomy-window-header-headline-gap, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
220
+ height:var(--sn-popupPrivate-anatomy-window-header-headline-height, var(--sn-density-size-m, var(--sn-primitive-dimension-56, 56px)));
221
+ padding-left:var(--sn-popupPrivate-anatomy-window-header-headline-paddingHorizontal, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
222
+ padding-right:var(--sn-popupPrivate-anatomy-window-header-headline-paddingHorizontal, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
223
+ padding-right:0;
224
+ }
225
+ [data-surface=window] .headlineWrapper:not([data-with-before]){
226
+ padding-left:0;
227
+ }
228
+
229
+ .headline{
230
+ display:flex;
231
+ align-items:center;
232
+ box-sizing:border-box;
233
+ min-width:0;
234
+ }
235
+ [data-surface=bottomSheet] .headline{
236
+ flex:1 1 auto;
237
+ gap:var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-headlineWrapper-gap, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
238
+ padding-top:var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-headlineWrapper-paddingVertical, var(--sn-primitive-dimension-10, 10px));
239
+ padding-bottom:var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-headlineWrapper-paddingVertical, var(--sn-primitive-dimension-10, 10px));
240
+ }
241
+ [data-surface=window] .headline{
242
+ flex:1 0 0;
243
+ gap:var(--sn-popupPrivate-anatomy-window-header-headline-headlineWrapper-gap, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px)));
244
+ padding-left:var(--sn-popupPrivate-anatomy-window-header-headline-headlineWrapper-paddingLeft, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
245
+ padding-right:var(--sn-popupPrivate-anatomy-window-header-headline-headlineWrapper-paddingRight, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
246
+ }
247
+
248
+ [data-surface=bottomSheet] .headlineWrapper:not([data-with-before]) .headline{
249
+ padding-left:var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-headlineWrapper-paddingLeft, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
250
+ }
251
+
252
+ [data-surface=bottomSheet] .headlineWrapper:not([data-with-after]) .headline{
253
+ padding-right:var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-headlineWrapper-paddingRight, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
254
+ }
255
+
256
+ [data-surface=window] .headlineWrapper[data-with-before] .headline{
257
+ padding-left:0;
258
+ }
259
+
260
+ .title{
261
+ flex:0 1 auto;
262
+ min-width:0;
263
+ color:var(--sn-theme-color-available-version-textMain, var(--sn-brand-color-text-version-main, #41424e));
264
+ }
265
+
266
+ .slotAfterTitle{
267
+ display:inline-flex;
268
+ flex:1 1 auto;
269
+ align-items:center;
270
+ min-width:0;
271
+ }
272
+
273
+ .actionButton{
274
+ display:inline-flex;
275
+ flex-shrink:0;
276
+ align-items:center;
277
+ }
278
+
279
+ .subtitleWrapper{
280
+ display:flex;
281
+ flex-direction:column;
282
+ align-items:flex-start;
283
+ box-sizing:border-box;
284
+ }
285
+ [data-surface=bottomSheet] .subtitleWrapper{
286
+ padding-right:var(--sn-popupPrivate-anatomy-bottomSheet-header-subHeadlineWrapper-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
287
+ padding-left:var(--sn-popupPrivate-anatomy-bottomSheet-header-subHeadlineWrapper-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
288
+ }
289
+ [data-surface=window] .subtitleWrapper{
290
+ align-self:stretch;
291
+ padding-left:var(--sn-popupPrivate-anatomy-window-header-subHeadlineWrapper-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
292
+ padding-right:var(--sn-popupPrivate-anatomy-window-header-subHeadlineWrapper-paddingHorizontal, var(--sn-density-spacing-interval-l, var(--sn-primitive-dimension-24, 24px)));
293
+ }
294
+
295
+ .subtitle{
296
+ color:var(--sn-theme-color-available-version-textSecondary, var(--sn-brand-color-text-version-secondary, rgba(65, 66, 78, 0.8)));
297
+ }
298
+
299
+ .secondWrapper{
300
+ box-sizing:border-box;
301
+ }
302
+ [data-surface=bottomSheet] .secondWrapper{
303
+ padding-left:var(--sn-popupPrivate-anatomy-bottomSheet-header-secondWrapper-paddingHorizontal, var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-headlineWrapper-gap, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px))));
304
+ padding-right:var(--sn-popupPrivate-anatomy-bottomSheet-header-secondWrapper-paddingHorizontal, var(--sn-popupPrivate-anatomy-bottomSheet-header-headline-headlineWrapper-gap, var(--sn-density-spacing-interval-s, var(--sn-primitive-dimension-8, 8px))));
305
+ }
306
+ .wrapper{
307
+ touch-action:none;
308
+ overflow:hidden;
309
+ display:flex;
310
+ flex-shrink:0;
311
+ align-items:center;
312
+ justify-content:center;
313
+ box-sizing:border-box;
314
+ width:100%;
315
+ }
316
+ .wrapper[data-media-kind=icon]{
317
+ padding-top:var(--sn-popupPrivate-anatomy-window-media-icon-paddingTop, var(--sn-primitive-dimension-32, 32px));
318
+ }
319
+
320
+ .image{
321
+ display:block;
322
+ width:100%;
323
+ min-height:184px;
324
+ -o-object-fit:cover;
325
+ object-fit:cover;
326
+ }
327
+
328
+ .icon{
329
+ display:block;
330
+ max-width:100%;
331
+ height:auto;
332
+ }
@@ -0,0 +1,95 @@
1
+ import { ButtonProps } from '@cloud-ru/ds-button';
2
+ import { ValueOf, WithSupportProps } from '@cloud-ru/ds-utils';
3
+ import { PropsWithChildren, ReactNode } from 'react';
4
+ import { FOOTER_ACTIONS_ORIENTATION, MEDIA_KIND } from './constants.js';
5
+ /**
6
+ * Пропсы action-кнопки футера — объект пропсов `Button` из `@cloud-ru/ds-button`. `view` / `appearance` имеют
7
+ * дефолты по слоту (переопределяемы); `size` (m) и full-width задаёт `ButtonGroup`, поэтому исключены.
8
+ * `data-test-id` слота фиксирует `TEST_IDS` (через `WithSupportProps` у `ButtonGroup`).
9
+ *
10
+ * По умолчанию рендерится как `<button>`. Для CTA-ссылки передайте `as='a'` + `href` (`Button`
11
+ * полиморфен и отрендерит `<a>` — доступен middle-click / контекстное меню). Union `button | anchor`
12
+ * ниже раскрывает анкорные атрибуты. Для нестандартного футера остаётся `footer: ReactNode`.
13
+ */
14
+ export type BottomSheetActionButton = Omit<ButtonProps<'button'>, 'fullWidth' | 'size'> | Omit<ButtonProps<'a'>, 'fullWidth' | 'size'>;
15
+ export type MediaKind = ValueOf<typeof MEDIA_KIND>;
16
+ export type FooterActionsOrientation = ValueOf<typeof FOOTER_ACTIONS_ORIENTATION>;
17
+ export type PopupMediaProps = {
18
+ /** URL изображения / иконки. */
19
+ src: string;
20
+ /** Альтернативный текст (a11y). */
21
+ alt: string;
22
+ /**
23
+ * Режим:
24
+ * - `'image'` — изображение во всю ширину (высота `184px`), прижато к шапке (убирается
25
+ * верхний отступ контент-блока). Горизонтальные паддинги body не затрагивает — для edge-to-edge body
26
+ * используйте `bodyPadding={false}` отдельно.
27
+ * - `'icon'` — иконка с `padding-top: 24px`.
28
+ * @default 'image'
29
+ */
30
+ kind?: MediaKind;
31
+ };
32
+ export type PopupHeaderProps = WithSupportProps<{
33
+ /** Заголовок. Типографика зависит от поверхности: `title-l` на sheet, `headline-s` на window (modal/drawer). */
34
+ title?: ReactNode;
35
+ /** `id` заголовка — для связи с `aria-labelledby` dialog'а (accessible name). */
36
+ titleId?: string;
37
+ /** Slot справа от title (например, `QuestionTooltip` из `@cloud-ru/ds-tooltip`). */
38
+ slotAfterTitle?: ReactNode;
39
+ /** Текстовая строка-подзаголовок под title (Figma `subtitleWrapper`). Рендерится на всех поверхностях. */
40
+ subtitle?: ReactNode;
41
+ /**
42
+ * Slot под подзаголовком (Figma `secondWrapper`) — типично `SearchBar`, `SegmentControl` или `Filter`.
43
+ * Есть только в мастере `bottomSheet`, поэтому рендерится **только** на sheet-поверхности.
44
+ */
45
+ slotSecondTitle?: ReactNode;
46
+ /**
47
+ * Callback клика на back-кнопку (слева в шапке).
48
+ * Наличие callback'а авто-рендерит `Button view='function' icon={<ArrowLeftSVG />}`.
49
+ */
50
+ onBackButtonClick?(): void;
51
+ /** Slot справа от headline-строки (любой `ReactNode`, обычно `Button` с иконкой). */
52
+ actionButton?: ReactNode;
53
+ /**
54
+ * Усечение строковых `title`/`subtitle` через `TruncateString` (число строк). Применяется только
55
+ * когда задано — по умолчанию текст не усекается. Актуально для window-поверхности (modal/drawer),
56
+ * где длинный заголовок иначе переносится на несколько строк.
57
+ */
58
+ truncate?: {
59
+ title?: number;
60
+ subtitle?: number;
61
+ };
62
+ /**
63
+ * Переопределение `data-test-id` слотов шапки. Каждый пропущенный ключ берётся из `TEST_IDS`.
64
+ * Потребитель-обёртка (drawer/modal) прокидывает сюда свои id, чтобы сохранить публичный контракт.
65
+ */
66
+ testIds?: PopupHeaderTestIds;
67
+ /** CSS-класс контейнера header'а. */
68
+ className?: string;
69
+ }>;
70
+ /** `data-test-id` слотов `PopupHeader` — для переопределения потребителем. */
71
+ export type PopupHeaderTestIds = {
72
+ header?: string;
73
+ title?: string;
74
+ slotAfterTitle?: string;
75
+ subtitle?: string;
76
+ slotSecondTitle?: string;
77
+ backButton?: string;
78
+ actionButton?: string;
79
+ };
80
+ export type PopupBodyProps = WithSupportProps<PropsWithChildren<{
81
+ /** Содержимое body (альтернатива `children`). */
82
+ content?: ReactNode;
83
+ /**
84
+ * Горизонтальные паддинги body. При `false` контент идёт во всю ширину sheet'а (edge-to-edge) —
85
+ * для карт, изображений, списков без отступов. Соответствует Figma-оси `padding=false`.
86
+ * @default true
87
+ */
88
+ bodyPadding?: boolean;
89
+ /** CSS-класс контейнера body. */
90
+ className?: string;
91
+ }>>;
92
+ export type PopupFooterProps = WithSupportProps<PropsWithChildren<{
93
+ /** CSS-класс контейнера footer'а. */
94
+ className?: string;
95
+ }>>;
@@ -0,0 +1 @@
1
+ export {};