@pushwoosh/dumb-components 4.2.1 → 4.3.1

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/PageHeader/components/PageHeader/PageHeader.js +29 -14
  2. package/PageHeader/components/PageHeader/styles.d.ts +16 -0
  3. package/PageHeader/components/PageHeader/styles.js +18 -3
  4. package/PageHeader/components/PageHeader/types.d.ts +2 -0
  5. package/PageHeader/constants.d.ts +2 -0
  6. package/PageHeader/constants.js +2 -0
  7. package/RichMessageEditor/assets/arrow.svg +1 -1
  8. package/Steps/components/Step/Step.d.ts +3 -0
  9. package/Steps/components/Step/Step.js +48 -0
  10. package/Steps/components/Step/index.d.ts +1 -0
  11. package/Steps/components/Step/index.js +1 -0
  12. package/Steps/components/Step/styles.d.ts +6 -0
  13. package/Steps/components/Step/styles.js +30 -0
  14. package/Steps/components/Step/types.d.ts +10 -0
  15. package/Steps/components/Step/types.js +1 -0
  16. package/Steps/components/Steps/Steps.d.ts +4 -0
  17. package/Steps/components/Steps/Steps.js +35 -0
  18. package/Steps/components/Steps/index.d.ts +1 -0
  19. package/Steps/components/Steps/index.js +1 -0
  20. package/Steps/components/StepsSkeleton/StepsSkeleton.d.ts +3 -0
  21. package/Steps/components/StepsSkeleton/StepsSkeleton.js +37 -0
  22. package/Steps/components/StepsSkeleton/index.d.ts +1 -0
  23. package/Steps/components/StepsSkeleton/index.js +1 -0
  24. package/Steps/components/StepsSkeleton/types.d.ts +3 -0
  25. package/Steps/components/StepsSkeleton/types.js +1 -0
  26. package/Steps/constants.d.ts +4 -0
  27. package/Steps/constants.js +5 -0
  28. package/Steps/helpers/index.d.ts +1 -0
  29. package/Steps/helpers/index.js +1 -0
  30. package/Steps/helpers/progress.d.ts +3 -0
  31. package/Steps/helpers/progress.js +7 -0
  32. package/Steps/hooks/index.d.ts +1 -0
  33. package/Steps/hooks/index.js +1 -0
  34. package/Steps/hooks/useSteps.d.ts +3 -0
  35. package/Steps/hooks/useSteps.js +67 -0
  36. package/Steps/index.d.ts +3 -0
  37. package/Steps/index.js +2 -0
  38. package/Steps/types.d.ts +59 -0
  39. package/Steps/types.js +1 -0
  40. package/index.d.ts +2 -0
  41. package/index.js +1 -0
  42. package/package.json +2 -2
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Columns } from '@pushwoosh/kit-helpers';
2
+ import { UnitSize } from '@pushwoosh/kit-constants';
3
+ import { Columns, Horizontal } from '@pushwoosh/kit-helpers';
3
4
  import { ArrowRoundIcon } from '@pushwoosh/kit-icons';
4
5
  import { H1 } from '@pushwoosh/kit-typography';
5
6
  import { Button } from '../../../Button';
6
7
  import { PageHeaderError } from '../PageHeaderError';
7
- import { RootBox, BackButton, TitleBox, DescriptionBox, ActionsBox, MoreActionsBox } from './styles';
8
+ import { RootBox, BackButton, TitleBox, TitleWithExtraBox, TitleWrapBox, TitleExtraBox, DescriptionBox, ActionsBox, MoreActionsBox } from './styles';
8
9
  /**
9
10
  * Page-level header with a title, optional back arrow, description, inline error bar
10
11
  * and submit/cancel/extra actions.
@@ -15,6 +16,7 @@ import { RootBox, BackButton, TitleBox, DescriptionBox, ActionsBox, MoreActionsB
15
16
  export function PageHeader(props) {
16
17
  const {
17
18
  title,
19
+ titleExtra,
18
20
  description,
19
21
  error,
20
22
  resolveErrorText,
@@ -29,23 +31,36 @@ export function PageHeader(props) {
29
31
  } = props;
30
32
  const hasActions = Boolean(onSubmit || onCancel);
31
33
  const hasActionsBox = hasActions || Boolean(moreActions);
34
+ const backButton = onBack && _jsx(BackButton, {
35
+ children: _jsx(Button, {
36
+ color: "primary",
37
+ view: "ghost",
38
+ onClick: onBack,
39
+ children: _jsx(ArrowRoundIcon, {
40
+ direction: "left",
41
+ size: "medium",
42
+ view: "filled"
43
+ })
44
+ })
45
+ });
32
46
  return _jsxs(RootBox, {
33
47
  children: [_jsxs(Columns, {
34
48
  "$sizes": "minmax(0, 1fr) max-content",
35
49
  "$gap": 24,
36
- children: [_jsxs(TitleBox, {
37
- children: [onBack && _jsx(BackButton, {
38
- children: _jsx(Button, {
39
- color: "primary",
40
- view: "ghost",
41
- onClick: onBack,
42
- children: _jsx(ArrowRoundIcon, {
43
- direction: "left",
44
- size: "medium",
45
- view: "filled"
50
+ children: [titleExtra ? _jsxs(TitleWithExtraBox, {
51
+ children: [backButton, _jsxs(TitleWrapBox, {
52
+ children: [_jsx(Horizontal, {
53
+ "$minHeight": UnitSize.FIELD_HEIGHT,
54
+ "$alignItems": "center",
55
+ children: _jsx(H1, {
56
+ children: title
46
57
  })
47
- })
48
- }), _jsx(H1, {
58
+ }), _jsx(TitleExtraBox, {
59
+ children: titleExtra
60
+ })]
61
+ })]
62
+ }) : _jsxs(TitleBox, {
63
+ children: [backButton, _jsx(H1, {
49
64
  children: title
50
65
  })]
51
66
  }), hasActionsBox && _jsxs(ActionsBox, {
@@ -8,6 +8,22 @@ export declare const BackButton: import("styled-components/dist/types").IStyledC
8
8
  export declare const TitleBox: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
9
9
  style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
10
10
  }> & string;
11
+ export declare const TitleWithExtraBox: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("styled-components").FastOmit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
12
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
13
+ }, "as" | "forwardedAs"> & {
14
+ as?: import("styled-components").WebTarget | undefined;
15
+ forwardedAs?: import("styled-components").WebTarget | undefined;
16
+ }, "style"> & {
17
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | (import("react").CSSProperties & {
18
+ [key: `--${string}`]: string | number | undefined;
19
+ }) | undefined;
20
+ }> & string;
21
+ export declare const TitleWrapBox: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
22
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
23
+ }> & string;
24
+ export declare const TitleExtraBox: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
25
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
26
+ }> & string;
11
27
  export declare const ActionsBox: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
12
28
  style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
13
29
  }> & string;
@@ -1,5 +1,7 @@
1
1
  import styled from 'styled-components';
2
+ import { FontSize, LineHeight, UnitSize } from '@pushwoosh/kit-constants';
2
3
  import { Paragraph } from '@pushwoosh/kit-typography';
4
+ import { PAGE_HEADER_INSET_ATTRIBUTE, STEP_CELL_INSET } from '../../constants';
3
5
  export const RootBox = styled.div.withConfig({
4
6
  displayName: "RootBox",
5
7
  componentId: "sc-1qwo99q-0"
@@ -12,17 +14,30 @@ export const TitleBox = styled.div.withConfig({
12
14
  displayName: "TitleBox",
13
15
  componentId: "sc-1qwo99q-2"
14
16
  })(["display:flex;align-items:center;justify-content:start;flex-shrink:0;gap:8px;"]);
17
+ const TITLE_LINE_HEIGHT = `calc(${FontSize.HEADING1} * ${LineHeight.HEADING1})`;
18
+ export const TitleWithExtraBox = styled(TitleBox).withConfig({
19
+ displayName: "TitleWithExtraBox",
20
+ componentId: "sc-1qwo99q-3"
21
+ })(["align-items:flex-start;flex-shrink:1;min-width:0;"]);
22
+ export const TitleWrapBox = styled.div.withConfig({
23
+ displayName: "TitleWrapBox",
24
+ componentId: "sc-1qwo99q-4"
25
+ })(["display:flex;flex-wrap:wrap;align-items:center;column-gap:8px;row-gap:calc(8px - (", " - ", ") / 2);min-width:0;&:has([", "]){--page-header-extra-inset:", "px;}"], UnitSize.FIELD_HEIGHT, TITLE_LINE_HEIGHT, PAGE_HEADER_INSET_ATTRIBUTE, STEP_CELL_INSET);
26
+ export const TitleExtraBox = styled.div.withConfig({
27
+ displayName: "TitleExtraBox",
28
+ componentId: "sc-1qwo99q-5"
29
+ })(["display:flex;align-items:center;min-width:0;margin-left:calc(-1 * var(--page-header-extra-inset,0px));"]);
15
30
  export const ActionsBox = styled.div.withConfig({
16
31
  displayName: "ActionsBox",
17
- componentId: "sc-1qwo99q-3"
32
+ componentId: "sc-1qwo99q-6"
18
33
  })(["display:flex;justify-content:flex-end;gap:8px;"]);
19
34
  export const MoreActionsBox = styled.div.withConfig({
20
35
  displayName: "MoreActionsBox",
21
- componentId: "sc-1qwo99q-4"
36
+ componentId: "sc-1qwo99q-7"
22
37
  })(["display:flex;margin-right:", ";"], ({
23
38
  $hasActions
24
39
  }) => $hasActions ? '16px' : '0');
25
40
  export const DescriptionBox = styled(Paragraph).withConfig({
26
41
  displayName: "DescriptionBox",
27
- componentId: "sc-1qwo99q-5"
42
+ componentId: "sc-1qwo99q-8"
28
43
  })(["margin-top:16px;"]);
@@ -3,6 +3,8 @@ import type { ReactElement, ReactNode } from 'react';
3
3
  export interface PageHeaderProps {
4
4
  /** Page title. */
5
5
  title: ReactNode;
6
+ /** Content next to the title, e.g. `Steps`; wraps under the title when the row is too narrow. */
7
+ titleExtra?: ReactNode;
6
8
  /** Subtitle/description under the title. */
7
9
  description?: ReactNode | string;
8
10
  /** Error message shown in an inline error bar. */
@@ -0,0 +1,2 @@
1
+ export declare const PAGE_HEADER_INSET_ATTRIBUTE = "data-page-header-inset";
2
+ export declare const STEP_CELL_INSET = 3;
@@ -0,0 +1,2 @@
1
+ export const PAGE_HEADER_INSET_ATTRIBUTE = 'data-page-header-inset';
2
+ export const STEP_CELL_INSET = 3;
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="10" height="20" fill="none"><mask id="a" fill="#fff"><path fill-rule="evenodd" d="M0 2a2 2 0 0 1 4 0v1.26a2 2 0 0 0 .598 1.426L8.55 8.574a2 2 0 0 1 0 2.852l-3.953 3.888A2 2 0 0 0 4 16.74V18a2 2 0 1 1-4 0z" clip-rule="evenodd"/></mask><path fill="#F7F9FA" fill-rule="evenodd" d="M0 2a2 2 0 0 1 4 0v1.26a2 2 0 0 0 .598 1.426L8.55 8.574a2 2 0 0 1 0 2.852l-3.953 3.888A2 2 0 0 0 4 16.74V18a2 2 0 1 1-4 0z" clip-rule="evenodd"/><path fill="#DCE1E5" d="m4.598 15.314.7.713zM3 3.26V2h2v1.26zm4.85 6.027L3.896 5.4 5.3 3.973 9.252 7.86zm-3.954 5.314 3.954-3.888 1.402 1.426L5.3 16.027zM3 18v-1.26h2V18zM1 2v16h-2V2zm1 19a3 3 0 0 1-3-3h2a1 1 0 0 0 1 1zm3-3a3 3 0 0 1-3 3v-2a1 1 0 0 0 1-1zm.299-1.973A1 1 0 0 0 5 16.74H3a3 3 0 0 1 .896-2.138L5.3 16.026zm3.953-8.166a3 3 0 0 1 0 4.278L7.85 10.713a1 1 0 0 0 0-1.426zM5 3.26a1 1 0 0 0 .299.713L3.896 5.399A3 3 0 0 1 3 3.259zM2 1a1 1 0 0 0-1 1h-2a3 3 0 0 1 3-3zm0-2a3 3 0 0 1 3 3H3a1 1 0 0 0-1-1z" mask="url(#a)"/><path stroke="#F7F9FA" stroke-width="3" d="M1.5 0v20"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="20" fill="none"><mask id="a" fill="#fff"><path fill-rule="evenodd" d="M0 2a2 2 0 0 1 4 0v1.26a2 2 0 0 0 .598 1.426L8.55 8.574a2 2 0 0 1 0 2.852l-3.953 3.888A2 2 0 0 0 4 16.74V18a2 2 0 1 1-4 0z" clip-rule="evenodd"/></mask><path fill="var(--pw-color-frozen, #F7F9FA)" fill-rule="evenodd" d="M0 2a2 2 0 0 1 4 0v1.26a2 2 0 0 0 .598 1.426L8.55 8.574a2 2 0 0 1 0 2.852l-3.953 3.888A2 2 0 0 0 4 16.74V18a2 2 0 1 1-4 0z" clip-rule="evenodd"/><path fill="var(--pw-color-form, #DCE1E5)" d="m4.598 15.314.7.713zM3 3.26V2h2v1.26zm4.85 6.027L3.896 5.4 5.3 3.973 9.252 7.86zm-3.954 5.314 3.954-3.888 1.402 1.426L5.3 16.027zM3 18v-1.26h2V18zM1 2v16h-2V2zm1 19a3 3 0 0 1-3-3h2a1 1 0 0 0 1 1zm3-3a3 3 0 0 1-3 3v-2a1 1 0 0 0 1-1zm.299-1.973A1 1 0 0 0 5 16.74H3a3 3 0 0 1 .896-2.138L5.3 16.026zm3.953-8.166a3 3 0 0 1 0 4.278L7.85 10.713a1 1 0 0 0 0-1.426zM5 3.26a1 1 0 0 0 .299.713L3.896 5.399A3 3 0 0 1 3 3.259zM2 1a1 1 0 0 0-1 1h-2a3 3 0 0 1 3-3zm0-2a3 3 0 0 1 3 3H3a1 1 0 0 0-1-1z" mask="url(#a)"/><path stroke="var(--pw-color-frozen, #F7F9FA)" stroke-width="3" d="M1.5 0v20"/></svg>
@@ -0,0 +1,3 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { StepProps } from './types';
3
+ export declare function Step(props: StepProps): ReactElement;
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Color } from '@pushwoosh/kit-constants';
3
+ import { Horizontal } from '@pushwoosh/kit-helpers';
4
+ import { CheckRoundIcon } from '@pushwoosh/kit-icons';
5
+ import { Text } from '@pushwoosh/kit-typography';
6
+ import { StepBox } from './styles';
7
+ const TONES = {
8
+ passed: 'success',
9
+ current: 'primary',
10
+ 'not-passed': 'muted'
11
+ };
12
+ const CELL_TYPES = {
13
+ passed: 'blank',
14
+ current: 'dot',
15
+ 'not-passed': 'blank'
16
+ };
17
+ export function Step(props) {
18
+ const {
19
+ title,
20
+ progress,
21
+ hasError,
22
+ hasTail,
23
+ onClick
24
+ } = props;
25
+ const isChecked = progress === 'passed' && !hasError;
26
+ return _jsxs(_Fragment, {
27
+ children: [_jsxs(StepBox, {
28
+ as: onClick ? 'button' : 'span',
29
+ type: onClick ? 'button' : undefined,
30
+ "$tone": hasError ? 'danger' : TONES[progress],
31
+ "aria-current": progress === 'current' ? 'step' : undefined,
32
+ onClick: onClick,
33
+ children: [_jsx(CheckRoundIcon, {
34
+ as: "span",
35
+ size: "medium",
36
+ type: isChecked ? 'fill' : CELL_TYPES[progress],
37
+ view: progress === 'current' || isChecked ? 'filled' : 'lined'
38
+ }), _jsx(Text, {
39
+ "$variant": "h1",
40
+ children: title
41
+ })]
42
+ }), hasTail && _jsx(Horizontal, {
43
+ "$width": 16,
44
+ "$height": 2,
45
+ "$bgColor": Color.FORM
46
+ })]
47
+ });
48
+ }
@@ -0,0 +1 @@
1
+ export { Step } from './Step';
@@ -0,0 +1 @@
1
+ export { Step } from './Step';
@@ -0,0 +1,6 @@
1
+ import type { StepTone } from './types';
2
+ export declare const StepBox: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Merged<Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "style"> & {
3
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
4
+ }, {
5
+ $tone: StepTone;
6
+ }>> & string;
@@ -0,0 +1,30 @@
1
+ import styled from 'styled-components';
2
+ import { Color } from '@pushwoosh/kit-constants';
3
+ const COLORS = {
4
+ muted: {
5
+ idle: Color.PHANTOM,
6
+ hover: Color.PRIMARY_HOVER
7
+ },
8
+ primary: {
9
+ idle: Color.PRIMARY,
10
+ hover: Color.PRIMARY_HOVER
11
+ },
12
+ success: {
13
+ idle: Color.SUCCESS,
14
+ hover: Color.SUCCESS_HOVER
15
+ },
16
+ danger: {
17
+ idle: Color.DANGER,
18
+ hover: Color.DANGER_HOVER
19
+ }
20
+ };
21
+ export const StepBox = styled.button.withConfig({
22
+ displayName: "StepBox",
23
+ componentId: "sc-goy1gs-0"
24
+ })(["display:flex;align-items:center;gap:6px;margin:0;padding:0;border:0;background:none;color:", ";white-space:nowrap;button&:hover{color:", ";cursor:pointer;}button&:active{color:", ";opacity:0.5;}&:focus-visible{outline:1px solid ", ";outline-offset:2px;}"], ({
25
+ $tone
26
+ }) => COLORS[$tone].idle, ({
27
+ $tone
28
+ }) => COLORS[$tone].hover, ({
29
+ $tone
30
+ }) => COLORS[$tone].hover, Color.BRIGHT);
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { StepProgress } from '../../types';
3
+ export type StepTone = 'muted' | 'primary' | 'success' | 'danger';
4
+ export type StepProps = {
5
+ title: ReactNode;
6
+ progress: StepProgress;
7
+ hasError: boolean;
8
+ hasTail: boolean;
9
+ onClick?: () => void;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { StepId, StepsProps } from '../../types';
3
+ /** Wizard progress for `PageHeader` `titleExtra`; spread `stepsProps` of {@link useSteps} into it. */
4
+ export declare function Steps<Id extends StepId>(props: StepsProps<Id>): ReactElement;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Horizontal } from '@pushwoosh/kit-helpers';
3
+ import { DEFAULT_SKELETON_COUNT, INSET_MARK } from '../../constants';
4
+ import { findStepIndex, getStepProgress } from '../../helpers';
5
+ import { Step } from '../Step';
6
+ import { StepsSkeleton } from '../StepsSkeleton';
7
+ /** Wizard progress for `PageHeader` `titleExtra`; spread `stepsProps` of {@link useSteps} into it. */
8
+ export function Steps(props) {
9
+ const {
10
+ items,
11
+ activeStep,
12
+ onStepClick,
13
+ isLoading = false,
14
+ skeletonCount = DEFAULT_SKELETON_COUNT
15
+ } = props;
16
+ const activeIndex = findStepIndex(items, activeStep);
17
+ if (isLoading) {
18
+ return _jsx(StepsSkeleton, {
19
+ count: items.length || skeletonCount
20
+ });
21
+ }
22
+ return _jsx(Horizontal, {
23
+ "$gap": 6,
24
+ "$alignItems": "center",
25
+ "$justifyContent": "start",
26
+ ...INSET_MARK,
27
+ children: items.map((item, index) => _jsx(Step, {
28
+ title: item.title,
29
+ progress: getStepProgress(index, activeIndex),
30
+ hasError: !!item.hasError,
31
+ hasTail: index < items.length - 1,
32
+ onClick: onStepClick && index < activeIndex ? () => onStepClick(item.id) : undefined
33
+ }, item.id))
34
+ });
35
+ }
@@ -0,0 +1 @@
1
+ export { Steps } from './Steps';
@@ -0,0 +1 @@
1
+ export { Steps } from './Steps';
@@ -0,0 +1,3 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { StepsSkeletonProps } from './types';
3
+ export declare function StepsSkeleton({ count }: StepsSkeletonProps): ReactElement;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment } from 'react';
3
+ import { Color } from '@pushwoosh/kit-constants';
4
+ import { Horizontal } from '@pushwoosh/kit-helpers';
5
+ import { CircularSkeleton, RectangularSkeleton } from '../../../Skeleton';
6
+ import { INSET_MARK } from '../../constants';
7
+ export function StepsSkeleton({
8
+ count
9
+ }) {
10
+ return _jsx(Horizontal, {
11
+ "$gap": 6,
12
+ "$alignItems": "center",
13
+ "$justifyContent": "start",
14
+ "aria-busy": "true",
15
+ ...INSET_MARK,
16
+ children: Array.from({
17
+ length: count
18
+ }, (_, index) => _jsxs(Fragment, {
19
+ children: [_jsx(Horizontal, {
20
+ "$padding": 3,
21
+ children: _jsx(CircularSkeleton, {
22
+ diameter: 18,
23
+ color: Color.SOFT_LIGHT
24
+ })
25
+ }), _jsx(RectangularSkeleton, {
26
+ width: 87,
27
+ height: 18,
28
+ borderRadius: 4,
29
+ color: Color.SOFT_LIGHT
30
+ }), index < count - 1 && _jsx(RectangularSkeleton, {
31
+ width: 16,
32
+ height: 2,
33
+ color: Color.SOFT_LIGHT
34
+ })]
35
+ }, index))
36
+ });
37
+ }
@@ -0,0 +1 @@
1
+ export { StepsSkeleton } from './StepsSkeleton';
@@ -0,0 +1 @@
1
+ export { StepsSkeleton } from './StepsSkeleton';
@@ -0,0 +1,3 @@
1
+ export type StepsSkeletonProps = {
2
+ count: number;
3
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare const DEFAULT_SKELETON_COUNT = 3;
2
+ export declare const INSET_MARK: {
3
+ "data-page-header-inset": boolean;
4
+ };
@@ -0,0 +1,5 @@
1
+ import { PAGE_HEADER_INSET_ATTRIBUTE } from '../PageHeader/constants';
2
+ export const DEFAULT_SKELETON_COUNT = 3;
3
+ export const INSET_MARK = {
4
+ [PAGE_HEADER_INSET_ATTRIBUTE]: true
5
+ };
@@ -0,0 +1 @@
1
+ export * from './progress';
@@ -0,0 +1 @@
1
+ export * from './progress';
@@ -0,0 +1,3 @@
1
+ import type { StepId, StepItem, StepProgress } from '../types';
2
+ export declare const findStepIndex: <Id extends StepId>(items: ReadonlyArray<StepItem<Id>>, id: Id | undefined) => number;
3
+ export declare const getStepProgress: (index: number, activeIndex: number) => StepProgress;
@@ -0,0 +1,7 @@
1
+ export const findStepIndex = (items, id) => id === undefined ? -1 : items.findIndex(item => item.id === id);
2
+ export const getStepProgress = (index, activeIndex) => {
3
+ if (activeIndex < 0 || index > activeIndex) {
4
+ return 'not-passed';
5
+ }
6
+ return index === activeIndex ? 'current' : 'passed';
7
+ };
@@ -0,0 +1 @@
1
+ export * from './useSteps';
@@ -0,0 +1 @@
1
+ export * from './useSteps';
@@ -0,0 +1,3 @@
1
+ import type { StepId, UseStepsOptions, UseStepsResult } from '../types';
2
+ /** Wizard state: a forward move passes `canLeave`, a backward move is never blocked. */
3
+ export declare function useSteps<Id extends StepId>(options: UseStepsOptions<Id>): UseStepsResult<Id>;
@@ -0,0 +1,67 @@
1
+ import { useCallback, useRef, useState } from 'react';
2
+ import { findStepIndex } from '../helpers';
3
+ /** Wizard state: a forward move passes `canLeave`, a backward move is never blocked. */
4
+ export function useSteps(options) {
5
+ const {
6
+ items,
7
+ activeStep,
8
+ onChange,
9
+ canLeave,
10
+ onBackward
11
+ } = options;
12
+ const activeIndex = findStepIndex(items, activeStep);
13
+ const [isTransitioning, setIsTransitioning] = useState(false);
14
+ const isPendingRef = useRef(false);
15
+ const goTo = useCallback(async id => {
16
+ const toIndex = findStepIndex(items, id);
17
+ if (isPendingRef.current || activeIndex < 0 || toIndex < 0 || toIndex === activeIndex) {
18
+ return;
19
+ }
20
+ if (toIndex < activeIndex) {
21
+ onBackward?.(activeStep, id);
22
+ onChange(id);
23
+ return;
24
+ }
25
+ isPendingRef.current = true;
26
+ setIsTransitioning(true);
27
+ try {
28
+ if (!canLeave || (await canLeave(activeStep, id))) {
29
+ onChange(id);
30
+ }
31
+ } catch (error) {
32
+ console.error('[useSteps]: canLeave threw, the move is refused.', error);
33
+ } finally {
34
+ isPendingRef.current = false;
35
+ setIsTransitioning(false);
36
+ }
37
+ }, [items, activeStep, activeIndex, onChange, canLeave, onBackward]);
38
+ const next = useCallback(async () => {
39
+ const nextItem = activeIndex >= 0 ? items[activeIndex + 1] : undefined;
40
+ if (nextItem) {
41
+ await goTo(nextItem.id);
42
+ }
43
+ }, [activeIndex, items, goTo]);
44
+ const prev = useCallback(() => {
45
+ const prevItem = activeIndex > 0 ? items[activeIndex - 1] : undefined;
46
+ if (prevItem) {
47
+ void goTo(prevItem.id);
48
+ }
49
+ }, [activeIndex, items, goTo]);
50
+ const onStepClick = useCallback(id => {
51
+ void goTo(id);
52
+ }, [goTo]);
53
+ return {
54
+ activeIndex,
55
+ isFirst: activeIndex === 0,
56
+ isLast: activeIndex >= 0 && activeIndex === items.length - 1,
57
+ goTo,
58
+ next,
59
+ prev,
60
+ isTransitioning,
61
+ stepsProps: {
62
+ items,
63
+ activeStep,
64
+ onStepClick
65
+ }
66
+ };
67
+ }
@@ -0,0 +1,3 @@
1
+ export { Steps } from './components/Steps';
2
+ export { useSteps } from './hooks';
3
+ export type { StepId, StepItem, StepProgress, StepsProps, UseStepsOptions, UseStepsResult, } from './types';
package/Steps/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { Steps } from './components/Steps';
2
+ export { useSteps } from './hooks';
@@ -0,0 +1,59 @@
1
+ import type { ReactNode } from 'react';
2
+ /** Identifier of a step: the form's own key, a string or a number. */
3
+ export type StepId = string | number;
4
+ /** One step of a {@link Steps} indicator. */
5
+ export type StepItem<Id extends StepId = StepId> = {
6
+ /** Stable key of the step; progress is derived from its position in `items`. */
7
+ id: Id;
8
+ /** Label: one word or two short ones. */
9
+ title: ReactNode;
10
+ /** Paints the step as erroneous; a passed step with an error is shown as not passed. */
11
+ hasError?: boolean;
12
+ };
13
+ /** Where a step stands relative to the active one. */
14
+ export type StepProgress = 'passed' | 'current' | 'not-passed';
15
+ /** Props of {@link Steps}. */
16
+ export type StepsProps<Id extends StepId = StepId> = {
17
+ /** Steps in display order, 2 to 5 of them. */
18
+ items: ReadonlyArray<StepItem<Id>>;
19
+ /** Id of the current step. */
20
+ activeStep: Id;
21
+ /** Makes the steps before the current one clickable; without it no step reacts to clicks. */
22
+ onStepClick?: (id: Id) => void;
23
+ /** Shows the skeleton instead of the steps — meant for the first data load only. */
24
+ isLoading?: boolean;
25
+ /** Number of skeleton steps when `items` is empty; 3 by default. */
26
+ skeletonCount?: number;
27
+ };
28
+ /** Options of {@link useSteps}. */
29
+ export type UseStepsOptions<Id extends StepId = StepId> = {
30
+ /** Steps in display order. */
31
+ items: ReadonlyArray<StepItem<Id>>;
32
+ /** Current step, stored by the caller (a form field, a route). */
33
+ activeStep: Id;
34
+ /** Called with the step to move to. */
35
+ onChange: (id: Id) => void;
36
+ /** Gate of a forward move; a Promise holds the move, `false` or a throw refuses it. */
37
+ canLeave?: (from: Id, to: Id) => boolean | Promise<boolean>;
38
+ /** Called before every backward move, which is never blocked; forms clear their errors here. */
39
+ onBackward?: (from: Id, to: Id) => void;
40
+ };
41
+ /** Result of {@link useSteps}. */
42
+ export type UseStepsResult<Id extends StepId = StepId> = {
43
+ /** Index of the current step in `items`, -1 when it is not there. */
44
+ activeIndex: number;
45
+ /** The current step is the first one. */
46
+ isFirst: boolean;
47
+ /** The current step is the last one. */
48
+ isLast: boolean;
49
+ /** Moves to a step, through `canLeave` when moving forward; ignored while a move is pending. */
50
+ goTo: (id: Id) => Promise<void>;
51
+ /** Moves to the next step; no-op on the last one. */
52
+ next: () => Promise<void>;
53
+ /** Moves to the previous step; no-op on the first one. */
54
+ prev: () => void;
55
+ /** A forward move is waiting for `canLeave`. */
56
+ isTransitioning: boolean;
57
+ /** Ready props for {@link Steps}: `items`, `activeStep`, `onStepClick`. */
58
+ stepsProps: Pick<StepsProps<Id>, 'items' | 'activeStep' | 'onStepClick'>;
59
+ };
package/Steps/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/index.d.ts CHANGED
@@ -40,6 +40,8 @@ export { WhatsAppPreview } from './WhatsAppPreview';
40
40
  export type { WhatsAppAttachment, WhatsAppAttachmentKind, WhatsAppMessage, WhatsAppPreviewProps, } from './WhatsAppPreview';
41
41
  export { Toast, ToastProvider, useToast } from './Toast';
42
42
  export { AutosizeInput, PageHeader, PageHeaderEditable } from './PageHeader';
43
+ export { Steps, useSteps } from './Steps';
44
+ export type { StepId, StepItem, StepProgress, StepsProps, UseStepsOptions, UseStepsResult, } from './Steps';
43
45
  export { ExportResult } from './ExportResult';
44
46
  export { ExternalImagePreview } from './ExternalImagePreview';
45
47
  export { NativeInput, NativeTextarea, NativeSelect, NativeTimeInput, } from './native';
package/index.js CHANGED
@@ -39,6 +39,7 @@ export { InboxPreview, resolveInboxLayout, MAX_CAROUSEL_SLIDES } from './InboxPr
39
39
  export { WhatsAppPreview } from './WhatsAppPreview';
40
40
  export { Toast, ToastProvider, useToast } from './Toast';
41
41
  export { AutosizeInput, PageHeader, PageHeaderEditable } from './PageHeader';
42
+ export { Steps, useSteps } from './Steps';
42
43
  export { ExportResult } from './ExportResult';
43
44
  export { ExternalImagePreview } from './ExternalImagePreview';
44
45
  export { NativeInput, NativeTextarea, NativeSelect, NativeTimeInput } from './native';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/dumb-components",
3
- "version": "4.2.1",
3
+ "version": "4.3.1",
4
4
  "description": "React components to build Pushwoosh products",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -38,7 +38,7 @@
38
38
  "@lezer/highlight": "^1.2.1",
39
39
  "@pushwoosh/kit-constants": "^3.0.0",
40
40
  "@pushwoosh/kit-helpers": "^4.16.0",
41
- "@pushwoosh/kit-icons": "^2.3.10",
41
+ "@pushwoosh/kit-icons": "^2.3.15",
42
42
  "@pushwoosh/kit-typography": "^2.0.0",
43
43
  "@tippyjs/react": "^4.2.6",
44
44
  "@uiw/codemirror-themes": "^4.23.8",