@sproutsocial/seeds-react-modal 1.1.1 → 2.0.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 (45) hide show
  1. package/.turbo/turbo-build.log +23 -23
  2. package/CHANGELOG.md +182 -0
  3. package/dist/ModalAction-BB7qJtQj.d.mts +445 -0
  4. package/dist/ModalAction-BB7qJtQj.d.ts +445 -0
  5. package/dist/esm/chunk-ETVICNHP.js +1353 -0
  6. package/dist/esm/chunk-ETVICNHP.js.map +1 -0
  7. package/dist/esm/index.js +11 -11
  8. package/dist/esm/index.js.map +1 -1
  9. package/dist/esm/v2/index.js +12 -20
  10. package/dist/index.d.mts +2 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +1154 -546
  13. package/dist/index.js.map +1 -1
  14. package/dist/v2/index.d.mts +4 -11
  15. package/dist/v2/index.d.ts +4 -11
  16. package/dist/v2/index.js +1152 -545
  17. package/dist/v2/index.js.map +1 -1
  18. package/package.json +8 -7
  19. package/src/index.ts +11 -12
  20. package/src/shared/constants.ts +11 -7
  21. package/src/v2/Modal.tsx +169 -0
  22. package/src/v2/ModalTypes.ts +343 -0
  23. package/src/v2/ModalV2.stories.tsx +413 -128
  24. package/src/v2/MotionConfig.ts +185 -0
  25. package/src/v2/components/ModalAction.tsx +94 -0
  26. package/src/v2/components/ModalBody.tsx +54 -0
  27. package/src/v2/components/ModalCloseWrapper.tsx +35 -0
  28. package/src/v2/components/ModalContent.tsx +288 -11
  29. package/src/v2/components/ModalDescription.tsx +14 -2
  30. package/src/v2/components/ModalFooter.tsx +94 -13
  31. package/src/v2/components/ModalHeader.tsx +77 -34
  32. package/src/v2/components/ModalOverlay.tsx +52 -0
  33. package/src/v2/components/ModalRail.tsx +35 -99
  34. package/src/v2/components/index.ts +11 -7
  35. package/src/v2/index.ts +13 -16
  36. package/dist/ModalRail-5PeilhW7.d.mts +0 -186
  37. package/dist/ModalRail-5PeilhW7.d.ts +0 -186
  38. package/dist/esm/chunk-4ITF4DBY.js +0 -717
  39. package/dist/esm/chunk-4ITF4DBY.js.map +0 -1
  40. package/src/v2/ModalV2.tsx +0 -388
  41. package/src/v2/ModalV2Styles.tsx +0 -180
  42. package/src/v2/ModalV2Types.ts +0 -154
  43. package/src/v2/components/ModalClose.tsx +0 -29
  44. package/src/v2/components/ModalCloseButton.tsx +0 -100
  45. package/src/v2/components/ModalTrigger.tsx +0 -39
@@ -1,19 +1,100 @@
1
1
  import * as React from "react";
2
- import { Footer } from "../ModalV2Styles";
3
- import { DEFAULT_MODAL_BG } from "../../shared/constants";
4
- import type { TypeModalV2FooterProps } from "../ModalV2Types";
2
+ import styled from "styled-components";
3
+ import Box from "@sproutsocial/seeds-react-box";
4
+ import {
5
+ COMMON,
6
+ FLEXBOX,
7
+ BORDER,
8
+ LAYOUT,
9
+ } from "@sproutsocial/seeds-react-system-props";
10
+ import { MOBILE_BREAKPOINT } from "../../shared/constants";
11
+ import type { TypeModalFooterProps } from "../ModalTypes";
12
+ import { ModalCloseWrapper } from "./ModalCloseWrapper";
5
13
 
6
- export const ModalFooter = (props: TypeModalV2FooterProps) => {
7
- const { bg = DEFAULT_MODAL_BG, children, ...rest } = props;
14
+ /**
15
+ * Base styled footer component for custom modal footers.
16
+ *
17
+ * Use this component when you need complete control over the footer layout
18
+ * and don't want to use the slot-based ModalFooter component.
19
+ *
20
+ * @example
21
+ * <ModalCustomFooter>
22
+ * <YourCustomFooterContent />
23
+ * </ModalCustomFooter>
24
+ */
25
+ export const ModalCustomFooter = styled(Box)`
26
+ flex: 0 0 auto;
27
+ font-family: ${(props) => props.theme.fontFamily};
28
+ background-color: ${(props) => props.theme.colors.container.background.base};
29
+ padding: ${(props) => props.theme.space[400]};
30
+ border-bottom-right-radius: ${(props) => props.theme.radii[800]};
31
+ border-bottom-left-radius: ${(props) => props.theme.radii[800]};
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: flex-end;
35
+ gap: ${(props) => props.theme.space[100]};
36
+
37
+ /* Flat bottom corners to blend with device edge on mobile */
38
+ @media (max-width: ${MOBILE_BREAKPOINT}) {
39
+ border-bottom-right-radius: 0;
40
+ border-bottom-left-radius: 0;
41
+ }
42
+
43
+ ${COMMON}
44
+ ${FLEXBOX}
45
+ ${BORDER}
46
+ ${LAYOUT}
47
+ `;
48
+
49
+ ModalCustomFooter.displayName = "ModalCustomFooter";
50
+
51
+ /**
52
+ * Modal footer component for action buttons.
53
+ *
54
+ * This component only supports slot-based rendering with button props.
55
+ * For custom footer layouts, use ModalCustomFooter instead.
56
+ *
57
+ * Provides automatic button wrapping and layout management. Supports three button positions:
58
+ * - primaryButton: Main action button on the right (auto-closes modal)
59
+ * - cancelButton: Secondary action on the right (auto-closes modal)
60
+ * - leftAction: Optional action on the left (no auto-close, for destructive actions)
61
+ *
62
+ * @example
63
+ * <ModalFooter
64
+ * cancelButton={<Button>Cancel</Button>}
65
+ * primaryButton={<Button appearance="primary">Save</Button>}
66
+ * />
67
+ *
68
+ * @example
69
+ * // With left action
70
+ * <ModalFooter
71
+ * leftAction={<Button appearance="destructive">Delete</Button>}
72
+ * cancelButton={<Button>Cancel</Button>}
73
+ * primaryButton={<Button appearance="primary">Save</Button>}
74
+ * />
75
+ */
76
+ export const ModalFooter = (props: TypeModalFooterProps) => {
77
+ const { cancelButton, primaryButton, leftAction, ...rest } = props;
78
+
79
+ // If no simplified props provided, return empty footer
80
+ if (!cancelButton && !primaryButton && !leftAction) {
81
+ return null;
82
+ }
83
+
84
+ // Build simplified API layout
8
85
  return (
9
- <Footer
10
- bg={bg}
11
- borderTop={500}
12
- borderColor="container.border.base"
13
- {...rest}
14
- >
15
- {children}
16
- </Footer>
86
+ <ModalCustomFooter {...rest}>
87
+ {/* Left action (e.g., Delete button) */}
88
+ {leftAction ? leftAction : null}
89
+
90
+ {/* Right-aligned button group (Cancel + Primary) */}
91
+ <Box display="flex" gap={300} marginLeft="auto">
92
+ {cancelButton && <ModalCloseWrapper>{cancelButton}</ModalCloseWrapper>}
93
+ {primaryButton && (
94
+ <ModalCloseWrapper>{primaryButton}</ModalCloseWrapper>
95
+ )}
96
+ </Box>
97
+ </ModalCustomFooter>
17
98
  );
18
99
  };
19
100
 
@@ -1,20 +1,74 @@
1
1
  import * as React from "react";
2
2
  import * as Dialog from "@radix-ui/react-dialog";
3
+ import styled from "styled-components";
3
4
  import Box from "@sproutsocial/seeds-react-box";
4
5
  import Text from "@sproutsocial/seeds-react-text";
5
- import { Header } from "../ModalV2Styles";
6
- import { ModalCloseButton } from "./ModalCloseButton";
7
- import type { TypeModalV2HeaderProps } from "../ModalV2Types";
8
- import { useDragContext } from "../ModalV2";
6
+ import {
7
+ COMMON,
8
+ FLEXBOX,
9
+ BORDER,
10
+ LAYOUT,
11
+ } from "@sproutsocial/seeds-react-system-props";
12
+ import type { TypeModalHeaderProps } from "../ModalTypes";
13
+ import { useDragContext } from "./ModalContent";
9
14
 
10
- export const ModalHeader = (props: TypeModalV2HeaderProps) => {
15
+ interface HeaderProps {
16
+ draggable?: boolean;
17
+ isDragging?: boolean;
18
+ }
19
+
20
+ /**
21
+ * Base styled header component for custom modal headers.
22
+ *
23
+ * Use this component when you need complete control over the header layout
24
+ * and don't want to use the slot-based ModalHeader component.
25
+ *
26
+ * @example
27
+ * <ModalCustomHeader>
28
+ * <YourCustomHeaderContent />
29
+ * </ModalCustomHeader>
30
+ */
31
+ export const ModalCustomHeader = styled(Box).withConfig({
32
+ shouldForwardProp: (prop) => !["draggable", "isDragging"].includes(prop),
33
+ })<HeaderProps>`
34
+ font-family: ${(props) => props.theme.fontFamily};
35
+ padding: ${(props) => props.theme.space[400]};
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: space-between;
39
+ flex: 0 0 auto;
40
+
41
+ /* Draggable cursor styling */
42
+ ${(props) =>
43
+ props.draggable &&
44
+ `
45
+ cursor: ${props.isDragging ? "grabbing" : "grab"};
46
+ user-select: none;
47
+ `}
48
+
49
+ ${COMMON}
50
+ ${FLEXBOX}
51
+ ${BORDER}
52
+ ${LAYOUT}
53
+ `;
54
+
55
+ ModalCustomHeader.displayName = "ModalCustomHeader";
56
+
57
+ /**
58
+ * Modal header component with title and subtitle slots.
59
+ *
60
+ * This component only supports slot-based rendering via title and subtitle props.
61
+ * For custom header layouts, use ModalCustomHeader instead.
62
+ *
63
+ * @example
64
+ * <ModalHeader title="Delete Item" subtitle="This action cannot be undone" />
65
+ */
66
+ export const ModalHeader = (props: TypeModalHeaderProps) => {
11
67
  const {
12
68
  title,
13
69
  subtitle,
14
- children,
15
70
  titleProps = {},
16
71
  subtitleProps = {},
17
- showInlineClose,
18
72
  ...rest
19
73
  } = props;
20
74
 
@@ -22,38 +76,27 @@ export const ModalHeader = (props: TypeModalV2HeaderProps) => {
22
76
  const isDraggable = dragContext?.draggable ?? false;
23
77
 
24
78
  return (
25
- <Header
79
+ <ModalCustomHeader
26
80
  {...rest}
27
81
  onMouseDown={isDraggable ? dragContext?.onHeaderMouseDown : undefined}
28
82
  draggable={isDraggable}
29
83
  isDragging={dragContext?.isDragging}
30
84
  >
31
- {children ? (
32
- children
33
- ) : (
34
- <React.Fragment>
35
- <Box>
36
- {title && (
37
- <Dialog.Title asChild {...titleProps}>
38
- <Text.Headline>{title}</Text.Headline>
39
- </Dialog.Title>
40
- )}
41
- {subtitle && (
42
- <Dialog.Description asChild {...subtitleProps}>
43
- <Text as="div" fontSize={200}>
44
- {subtitle}
45
- </Text>
46
- </Dialog.Description>
47
- )}
48
- </Box>
49
- {showInlineClose && (
50
- <Box display="flex" alignItems="center" justifyContent="flex-end">
51
- <ModalCloseButton position="relative" offset={0} />
52
- </Box>
53
- )}
54
- </React.Fragment>
55
- )}
56
- </Header>
85
+ <Box>
86
+ {title && (
87
+ <Dialog.Title asChild {...titleProps}>
88
+ <Text.Headline>{title}</Text.Headline>
89
+ </Dialog.Title>
90
+ )}
91
+ {subtitle && (
92
+ <Dialog.Description asChild {...subtitleProps}>
93
+ <Text as="div" fontSize={200}>
94
+ {subtitle}
95
+ </Text>
96
+ </Dialog.Description>
97
+ )}
98
+ </Box>
99
+ </ModalCustomHeader>
57
100
  );
58
101
  };
59
102
 
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import styled from "styled-components";
3
+ import { motion } from "motion/react";
4
+ import {
5
+ COMMON,
6
+ BORDER,
7
+ LAYOUT,
8
+ POSITION,
9
+ type TypeSystemCommonProps,
10
+ type TypeSystemBorderProps,
11
+ type TypeSystemLayoutProps,
12
+ type TypeSystemPositionProps,
13
+ } from "@sproutsocial/seeds-react-system-props";
14
+
15
+ interface StyledOverlayProps
16
+ extends TypeSystemCommonProps,
17
+ TypeSystemBorderProps,
18
+ TypeSystemLayoutProps,
19
+ TypeSystemPositionProps {
20
+ allowInteraction?: boolean;
21
+ }
22
+
23
+ // Styled motion.div for the overlay wrapper
24
+ export const StyledMotionOverlay = styled(motion.div)`
25
+ position: fixed;
26
+ top: 0px;
27
+ left: 0px;
28
+ right: 0px;
29
+ bottom: 0px;
30
+ `;
31
+
32
+ export const StyledOverlay = styled.div.withConfig({
33
+ shouldForwardProp: (prop) => !["allowInteraction"].includes(prop),
34
+ })<StyledOverlayProps>`
35
+ width: 100%;
36
+ height: 100%;
37
+ background-color: ${(props) => props.theme.colors.overlay.background.base};
38
+
39
+ /* Allow clicking through overlay when modal is draggable */
40
+ ${(props) =>
41
+ props.allowInteraction &&
42
+ `
43
+ pointer-events: none;
44
+ `}
45
+
46
+ ${COMMON}
47
+ ${BORDER}
48
+ ${LAYOUT}
49
+ ${POSITION}
50
+ `;
51
+
52
+ StyledOverlay.displayName = "ModalOverlay";
@@ -1,123 +1,59 @@
1
- // components/ModalRail.tsx
2
1
  import * as React from "react";
3
- import * as Dialog from "@radix-ui/react-dialog";
4
2
  import styled from "styled-components";
5
- import Icon from "@sproutsocial/seeds-react-icon";
6
- import type { TypeModalRailProps, TypeModalActionProps } from "../ModalV2Types";
7
-
8
- // --- styled wrappers ---
9
- const Rail = styled.div<{
10
- side: "right" | "left";
11
- offset: number;
12
- gap: number;
13
- size: number;
14
- }>`
3
+ import type { TypeModalRailProps } from "../ModalTypes";
4
+ import {
5
+ MOBILE_BREAKPOINT,
6
+ RAIL_BUTTON_SIZE,
7
+ RAIL_OFFSET,
8
+ RAIL_GAP,
9
+ } from "../../shared/constants";
10
+
11
+ /**
12
+ * Container for floating action buttons displayed alongside the modal.
13
+ *
14
+ * The rail provides a vertical column of action buttons positioned to the right of the modal
15
+ * on desktop, and switches to a horizontal row positioned above the modal on mobile devices.
16
+ * Always includes a close button by default, with support for additional custom actions.
17
+ *
18
+ * Actions are rendered through the Modal's `actions` prop or by directly including
19
+ * ModalAction components as children.
20
+ *
21
+ * @example
22
+ * <ModalRail>
23
+ * <ModalAction actionType="close" aria-label="Close" iconName="x-outline" />
24
+ * <ModalAction aria-label="Expand" iconName="arrows-pointing-out" onClick={handleExpand} />
25
+ * </ModalRail>
26
+ */
27
+ const Rail = styled.div`
15
28
  position: absolute;
16
- top: ${(p) => p.offset}px;
17
- ${(p) =>
18
- p.side === "right"
19
- ? `right: calc(-1 * (${p.size}px + ${p.offset}px));`
20
- : `left: calc(-1 * (${p.size}px + ${p.offset}px));`}
29
+ top: ${RAIL_OFFSET}px;
30
+ right: calc(-1 * (${RAIL_BUTTON_SIZE}px + ${RAIL_OFFSET}px));
21
31
  display: flex;
22
32
  flex-direction: column;
23
- gap: ${(p) => p.gap}px;
33
+ gap: ${RAIL_GAP}px;
24
34
  z-index: 1;
25
35
 
26
- @media (max-width: 400px) {
36
+ @media (max-width: ${MOBILE_BREAKPOINT}) {
27
37
  /* Move rail above the modal on the right side */
28
38
  top: auto;
29
- bottom: calc(100% + ${(p) => p.offset}px);
30
- right: ${(p) => p.offset}px;
39
+ bottom: calc(100% + ${RAIL_OFFSET}px);
40
+ right: ${RAIL_OFFSET}px;
31
41
  left: auto;
32
42
  /* Change to horizontal layout with reversed order */
33
43
  flex-direction: row-reverse;
34
44
  }
35
45
  `;
36
46
 
37
- const RailBtn = styled.button<{ size: number }>`
38
- width: ${(p) => p.size}px;
39
- height: ${(p) => p.size}px;
40
- display: inline-grid;
41
- place-items: center;
42
- border-radius: 8px;
43
- border: none;
44
- background: rgba(22, 32, 32, 0.56);
45
- color: #ffffff;
46
- cursor: pointer;
47
- outline: none;
48
- transition: all 0.2s ease;
49
-
50
- &:hover {
51
- background: rgba(22, 32, 32, 0.7);
52
- transform: translateY(-1px);
53
- }
54
-
55
- &:active {
56
- transform: translateY(0);
57
- }
58
-
59
- &:focus-visible {
60
- box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #205bc3;
61
- }
62
-
63
- &:disabled {
64
- opacity: 0.5;
65
- cursor: not-allowed;
66
- }
67
- `;
68
-
69
- // --- components ---
70
- export const ModalRail: React.FC<TypeModalRailProps> = ({
71
- side = "right",
72
- offset = 12,
73
- gap = 12,
74
- size = 44,
75
- children,
76
- }) => {
47
+ export const ModalRail: React.FC<TypeModalRailProps> = ({ children }) => {
77
48
  return (
78
49
  <Rail
79
50
  data-slot="modal-rail"
80
- side={side}
81
- offset={offset}
82
- gap={gap}
83
- size={size}
51
+ data-qa-modal-rail
84
52
  aria-label="Modal quick actions"
85
53
  >
86
- {React.Children.map(children, (child) =>
87
- React.isValidElement(child)
88
- ? React.cloneElement(child as any, { size })
89
- : child
90
- )}
54
+ {children}
91
55
  </Rail>
92
56
  );
93
57
  };
94
58
 
95
- export const ModalAction: React.FC<
96
- TypeModalActionProps & { size?: number }
97
- > = ({
98
- "aria-label": ariaLabel,
99
- iconName,
100
- disabled,
101
- size = 44,
102
- actionType,
103
- onClick,
104
- ...rest
105
- }) => {
106
- const Btn = (
107
- <RailBtn
108
- size={size}
109
- aria-label={ariaLabel}
110
- title={ariaLabel}
111
- disabled={disabled}
112
- {...rest}
113
- >
114
- {iconName ? <Icon name={iconName} size="small" /> : ariaLabel}
115
- </RailBtn>
116
- );
117
-
118
- return actionType === "close" ? (
119
- <Dialog.Close asChild>{Btn}</Dialog.Close>
120
- ) : (
121
- React.cloneElement(Btn, { onClick })
122
- );
123
- };
59
+ ModalRail.displayName = "ModalRail";
@@ -1,8 +1,12 @@
1
- export { ModalHeader } from "./ModalHeader";
2
- export { ModalFooter } from "./ModalFooter";
3
- export { ModalContent } from "./ModalContent";
1
+ // Public API exports
2
+ export { ModalHeader, ModalCustomHeader } from "./ModalHeader";
3
+ export { ModalFooter, ModalCustomFooter } from "./ModalFooter";
4
+ export { ModalBody } from "./ModalBody";
4
5
  export { ModalDescription } from "./ModalDescription";
5
- export { ModalCloseButton } from "./ModalCloseButton";
6
- export { ModalTrigger } from "./ModalTrigger";
7
- export { ModalClose } from "./ModalClose";
8
- export { ModalRail, ModalAction } from "./ModalRail";
6
+ export { ModalCloseWrapper } from "./ModalCloseWrapper";
7
+ export { ModalRail } from "./ModalRail";
8
+ export { ModalAction } from "./ModalAction";
9
+
10
+ // Internal exports for Modal usage only - not re-exported to public API
11
+ export { StyledOverlay, StyledMotionOverlay } from "./ModalOverlay";
12
+ export { DraggableModalContent, StaticModalContent } from "./ModalContent";
package/src/v2/index.ts CHANGED
@@ -1,37 +1,34 @@
1
1
  // Modal - Explicit named exports for optimal tree shaking
2
- export { default as Modal, useDragContext } from "./ModalV2";
2
+ export { default as Modal } from "./Modal";
3
3
  export {
4
4
  ModalHeader,
5
5
  ModalFooter,
6
- ModalContent,
6
+ ModalBody,
7
7
  ModalDescription,
8
- ModalCloseButton,
9
- ModalTrigger,
10
- ModalClose,
8
+ ModalCloseWrapper,
11
9
  ModalRail,
12
10
  ModalAction,
11
+ ModalCustomHeader,
12
+ ModalCustomFooter,
13
13
  } from "./components";
14
14
 
15
15
  // Explicit type exports
16
16
  export type {
17
- TypeModalV2Props,
18
- TypeModalV2TriggerProps,
19
- TypeModalV2HeaderProps,
20
- TypeModalV2FooterProps,
21
- TypeModalV2ContentProps,
22
- TypeModalV2DescriptionProps,
23
- TypeModalV2CloseButtonProps,
17
+ TypeModalProps,
18
+ TypeModalHeaderProps,
19
+ TypeModalFooterProps,
20
+ TypeModalBodyProps,
21
+ TypeModalDescriptionProps,
24
22
  TypeModalRailProps,
25
23
  TypeModalActionProps,
26
- } from "./ModalV2Types";
24
+ } from "./ModalTypes";
25
+
26
+ export type { ModalCloseWrapperProps } from "./components/ModalCloseWrapper";
27
27
 
28
28
  // Explicit constant exports
29
29
  export {
30
- DEFAULT_MODAL_Z_INDEX,
31
- DEFAULT_OVERLAY_Z_INDEX_OFFSET,
32
30
  DEFAULT_MODAL_WIDTH,
33
31
  DEFAULT_MODAL_BG,
34
32
  BODY_PADDING,
35
33
  MODAL_SIZE_PRESETS,
36
- MODAL_PRIORITY_Z_INDEX,
37
34
  } from "../shared/constants";