@sproutsocial/seeds-react-modal 1.0.2 → 1.0.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 (54) hide show
  1. package/.turbo/turbo-build.log +39 -19
  2. package/CHANGELOG.md +8 -0
  3. package/dist/Modal-ki8oiGbC.d.mts +69 -0
  4. package/dist/Modal-ki8oiGbC.d.ts +69 -0
  5. package/dist/ModalRail-OQ8DZ1vH.d.mts +178 -0
  6. package/dist/ModalRail-OQ8DZ1vH.d.ts +178 -0
  7. package/dist/esm/chunk-GKQRFPCX.js +642 -0
  8. package/dist/esm/chunk-GKQRFPCX.js.map +1 -0
  9. package/dist/esm/chunk-IYDY4OPB.js +237 -0
  10. package/dist/esm/chunk-IYDY4OPB.js.map +1 -0
  11. package/dist/esm/index.js +28 -235
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/v1/index.js +9 -0
  14. package/dist/esm/v1/index.js.map +1 -0
  15. package/dist/esm/v2/index.js +39 -0
  16. package/dist/esm/v2/index.js.map +1 -0
  17. package/dist/index.d.mts +11 -66
  18. package/dist/index.d.ts +11 -66
  19. package/dist/index.js +658 -17
  20. package/dist/index.js.map +1 -1
  21. package/dist/v1/index.d.mts +11 -0
  22. package/dist/v1/index.d.ts +11 -0
  23. package/dist/v1/index.js +273 -0
  24. package/dist/v1/index.js.map +1 -0
  25. package/dist/v2/index.d.mts +26 -0
  26. package/dist/v2/index.d.ts +26 -0
  27. package/dist/v2/index.js +694 -0
  28. package/dist/v2/index.js.map +1 -0
  29. package/package.json +33 -13
  30. package/src/Modal.stories.tsx +1 -1
  31. package/src/__tests__/v1/Modal.test.tsx +134 -0
  32. package/src/__tests__/v1/Modal.typetest.tsx +209 -0
  33. package/src/index.ts +36 -3
  34. package/src/shared/constants.ts +28 -0
  35. package/src/v1/Modal.tsx +159 -0
  36. package/src/v1/ModalTypes.ts +67 -0
  37. package/src/v1/index.ts +14 -0
  38. package/src/v1/styles.tsx +141 -0
  39. package/src/v2/ModalV2.stories.tsx +282 -0
  40. package/src/v2/ModalV2.tsx +306 -0
  41. package/src/v2/ModalV2Styles.tsx +150 -0
  42. package/src/v2/ModalV2Types.ts +158 -0
  43. package/src/v2/components/ModalClose.tsx +29 -0
  44. package/src/v2/components/ModalCloseButton.tsx +100 -0
  45. package/src/v2/components/ModalContent.tsx +16 -0
  46. package/src/v2/components/ModalDescription.tsx +19 -0
  47. package/src/v2/components/ModalFooter.tsx +20 -0
  48. package/src/v2/components/ModalHeader.tsx +52 -0
  49. package/src/v2/components/ModalRail.tsx +121 -0
  50. package/src/v2/components/ModalTrigger.tsx +39 -0
  51. package/src/v2/components/index.ts +8 -0
  52. package/src/v2/index.ts +37 -0
  53. package/tsconfig.json +7 -1
  54. package/tsup.config.ts +5 -1
@@ -0,0 +1,100 @@
1
+ import * as React from "react";
2
+ import * as Dialog from "@radix-ui/react-dialog";
3
+ import styled from "styled-components";
4
+ import Icon from "@sproutsocial/seeds-react-icon";
5
+ import type { TypeModalV2CloseButtonProps } from "../ModalV2Types";
6
+
7
+ const CloseButtonWrapper = styled.button<{
8
+ size?: number;
9
+ position?: "absolute" | "relative";
10
+ side?: "right" | "left";
11
+ offset?: number;
12
+ }>`
13
+ width: ${(p) => p.size || 44}px;
14
+ height: ${(p) => p.size || 44}px;
15
+ display: inline-grid;
16
+ place-items: center;
17
+ border-radius: 8px;
18
+ border: none;
19
+ background: rgba(22, 32, 32, 0.56);
20
+ color: #ffffff;
21
+ cursor: pointer;
22
+ outline: none;
23
+ transition: all 0.2s ease;
24
+
25
+ ${(p) =>
26
+ p.position === "absolute" &&
27
+ `
28
+ position: absolute;
29
+ top: 0px;
30
+ ${p.side || "right"}: ${p.offset || -48}px;
31
+ z-index: 1;
32
+ `}
33
+
34
+ &:hover {
35
+ background: rgba(22, 32, 32, 0.7);
36
+ transform: translateY(-1px);
37
+ }
38
+
39
+ &:active {
40
+ transform: translateY(0);
41
+ }
42
+
43
+ &:focus-visible {
44
+ box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #205bc3;
45
+ }
46
+
47
+ &:disabled {
48
+ opacity: 0.5;
49
+ cursor: not-allowed;
50
+ }
51
+ `;
52
+
53
+ export const ModalCloseButton = (props: TypeModalV2CloseButtonProps) => {
54
+ const {
55
+ children,
56
+ onClick,
57
+ asChild,
58
+ closeButtonLabel = "Close modal",
59
+ size = 44,
60
+ position = "absolute",
61
+ side = "right",
62
+ offset = -48,
63
+ ...rest
64
+ } = props;
65
+
66
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
67
+ onClick?.(e);
68
+ // Dialog.Close automatically handles closing
69
+ };
70
+
71
+ if (asChild) {
72
+ return (
73
+ <Dialog.Close asChild>
74
+ {React.cloneElement(children as React.ReactElement, {
75
+ onClick: handleClick,
76
+ ...rest,
77
+ })}
78
+ </Dialog.Close>
79
+ );
80
+ }
81
+
82
+ return (
83
+ <Dialog.Close asChild>
84
+ <CloseButtonWrapper
85
+ onClick={handleClick}
86
+ size={size}
87
+ position={position}
88
+ side={side}
89
+ offset={offset}
90
+ aria-label={closeButtonLabel}
91
+ title={closeButtonLabel}
92
+ {...rest}
93
+ >
94
+ {children || <Icon name="x-outline" size="small" />}
95
+ </CloseButtonWrapper>
96
+ </Dialog.Close>
97
+ );
98
+ };
99
+
100
+ ModalCloseButton.displayName = "ModalCloseButton";
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { Content } from "../ModalV2Styles";
3
+ import type { TypeModalV2ContentProps } from "../ModalV2Types";
4
+
5
+ export const ModalContent = React.forwardRef<
6
+ HTMLDivElement,
7
+ TypeModalV2ContentProps
8
+ >(({ children, ...rest }, ref) => {
9
+ return (
10
+ <Content data-qa-modal ref={ref} {...rest}>
11
+ {children}
12
+ </Content>
13
+ );
14
+ });
15
+
16
+ ModalContent.displayName = "ModalContent";
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import * as Dialog from "@radix-ui/react-dialog";
3
+ import Box from "@sproutsocial/seeds-react-box";
4
+ import type { TypeModalV2DescriptionProps } from "../ModalV2Types";
5
+
6
+ export const ModalDescription = React.forwardRef<
7
+ HTMLDivElement,
8
+ TypeModalV2DescriptionProps
9
+ >(({ children, descriptionProps = {}, ...rest }, ref) => {
10
+ return (
11
+ <Dialog.Description asChild {...descriptionProps}>
12
+ <Box ref={ref} {...rest}>
13
+ {children}
14
+ </Box>
15
+ </Dialog.Description>
16
+ );
17
+ });
18
+
19
+ ModalDescription.displayName = "ModalDescription";
@@ -0,0 +1,20 @@
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";
5
+
6
+ export const ModalFooter = (props: TypeModalV2FooterProps) => {
7
+ const { bg = DEFAULT_MODAL_BG, children, ...rest } = props;
8
+ return (
9
+ <Footer
10
+ bg={bg}
11
+ borderTop={500}
12
+ borderColor="container.border.base"
13
+ {...rest}
14
+ >
15
+ {children}
16
+ </Footer>
17
+ );
18
+ };
19
+
20
+ ModalFooter.displayName = "ModalFooter";
@@ -0,0 +1,52 @@
1
+ import * as React from "react";
2
+ import * as Dialog from "@radix-ui/react-dialog";
3
+ import Box from "@sproutsocial/seeds-react-box";
4
+ import Text from "@sproutsocial/seeds-react-text";
5
+ import { Header } from "../ModalV2Styles";
6
+ import { ModalCloseButton } from "./ModalCloseButton";
7
+ import type { TypeModalV2HeaderProps } from "../ModalV2Types";
8
+
9
+ export const ModalHeader = (props: TypeModalV2HeaderProps) => {
10
+ const {
11
+ title,
12
+ subtitle,
13
+ children,
14
+ bordered,
15
+ titleProps = {},
16
+ subtitleProps = {},
17
+ showInlineClose,
18
+ ...rest
19
+ } = props;
20
+
21
+ return (
22
+ <Header {...rest}>
23
+ {children ? (
24
+ children
25
+ ) : (
26
+ <React.Fragment>
27
+ <Box>
28
+ {title && (
29
+ <Dialog.Title asChild {...titleProps}>
30
+ <Text.Headline>{title}</Text.Headline>
31
+ </Dialog.Title>
32
+ )}
33
+ {subtitle && (
34
+ <Dialog.Description asChild {...subtitleProps}>
35
+ <Text as="div" fontSize={200}>
36
+ {subtitle}
37
+ </Text>
38
+ </Dialog.Description>
39
+ )}
40
+ </Box>
41
+ {showInlineClose && (
42
+ <Box display="flex" alignItems="center" justifyContent="flex-end">
43
+ <ModalCloseButton position="relative" offset={0} />
44
+ </Box>
45
+ )}
46
+ </React.Fragment>
47
+ )}
48
+ </Header>
49
+ );
50
+ };
51
+
52
+ ModalHeader.displayName = "ModalHeader";
@@ -0,0 +1,121 @@
1
+ // components/ModalRail.tsx
2
+ import * as React from "react";
3
+ import * as Dialog from "@radix-ui/react-dialog";
4
+ import styled from "styled-components";
5
+ import Icon from "@sproutsocial/seeds-react-icon"; // adjust import if path differs
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
+ collapseAt: number;
15
+ }>`
16
+ position: absolute;
17
+ top: ${(p) => p.offset}px;
18
+ ${(p) =>
19
+ p.side === "right"
20
+ ? `right: calc(-1 * (${p.size}px + ${p.offset}px));`
21
+ : `left: calc(-1 * (${p.size}px + ${p.offset}px));`}
22
+ display: grid;
23
+ grid-auto-flow: row;
24
+ gap: ${(p) => p.gap}px;
25
+ z-index: 1;
26
+
27
+ @media (max-width: ${(p) => p.collapseAt}px) {
28
+ ${(p) =>
29
+ p.side === "right" ? `right: ${p.offset}px;` : `left: ${p.offset}px;`}
30
+ }
31
+ `;
32
+
33
+ const RailBtn = styled.button<{ size: number }>`
34
+ width: ${(p) => p.size}px;
35
+ height: ${(p) => p.size}px;
36
+ display: inline-grid;
37
+ place-items: center;
38
+ border-radius: 8px;
39
+ border: none;
40
+ background: rgba(22, 32, 32, 0.56);
41
+ color: #ffffff;
42
+ cursor: pointer;
43
+ outline: none;
44
+ transition: all 0.2s ease;
45
+
46
+ &:hover {
47
+ background: rgba(22, 32, 32, 0.7);
48
+ transform: translateY(-1px);
49
+ }
50
+
51
+ &:active {
52
+ transform: translateY(0);
53
+ }
54
+
55
+ &:focus-visible {
56
+ box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #205bc3;
57
+ }
58
+
59
+ &:disabled {
60
+ opacity: 0.5;
61
+ cursor: not-allowed;
62
+ }
63
+ `;
64
+
65
+ // --- components ---
66
+ export const ModalRail: React.FC<TypeModalRailProps> = ({
67
+ side = "right",
68
+ offset = 12,
69
+ gap = 12,
70
+ size = 44,
71
+ collapseAt = 640,
72
+ children,
73
+ }) => {
74
+ return (
75
+ <Rail
76
+ data-slot="modal-rail"
77
+ side={side}
78
+ offset={offset}
79
+ gap={gap}
80
+ size={size}
81
+ collapseAt={collapseAt}
82
+ aria-label="Modal quick actions"
83
+ >
84
+ {React.Children.map(children, (child) =>
85
+ React.isValidElement(child)
86
+ ? React.cloneElement(child as any, { size })
87
+ : child
88
+ )}
89
+ </Rail>
90
+ );
91
+ };
92
+
93
+ export const ModalAction: React.FC<
94
+ TypeModalActionProps & { size?: number }
95
+ > = ({
96
+ "aria-label": ariaLabel,
97
+ iconName,
98
+ disabled,
99
+ size = 44,
100
+ actionType,
101
+ onClick,
102
+ ...rest
103
+ }) => {
104
+ const Btn = (
105
+ <RailBtn
106
+ size={size}
107
+ aria-label={ariaLabel}
108
+ title={ariaLabel}
109
+ disabled={disabled}
110
+ {...rest}
111
+ >
112
+ {iconName ? <Icon name={iconName} size="small" /> : ariaLabel}
113
+ </RailBtn>
114
+ );
115
+
116
+ return actionType === "close" ? (
117
+ <Dialog.Close asChild>{Btn}</Dialog.Close>
118
+ ) : (
119
+ React.cloneElement(Btn, { onClick })
120
+ );
121
+ };
@@ -0,0 +1,39 @@
1
+ import * as React from "react";
2
+ import * as Dialog from "@radix-ui/react-dialog";
3
+ import Button from "@sproutsocial/seeds-react-button";
4
+ import type { TypeModalV2TriggerProps } from "../ModalV2Types";
5
+
6
+ /**
7
+ * A trigger button that opens the modal when clicked.
8
+ * Must be used inside a Modal component's Dialog.Root context.
9
+ * Uses Seeds Button by default but supports asChild for custom elements.
10
+ */
11
+ export const ModalTrigger = (props: TypeModalV2TriggerProps) => {
12
+ const { children, onClick, asChild, ...rest } = props;
13
+
14
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
15
+ onClick?.(e);
16
+ // Dialog.Trigger automatically handles modal opening
17
+ };
18
+
19
+ if (asChild) {
20
+ return (
21
+ <Dialog.Trigger asChild>
22
+ {React.cloneElement(children as React.ReactElement, {
23
+ onClick: handleClick,
24
+ ...rest,
25
+ })}
26
+ </Dialog.Trigger>
27
+ );
28
+ }
29
+
30
+ return (
31
+ <Dialog.Trigger asChild>
32
+ <Button onClick={handleClick} {...rest}>
33
+ {children}
34
+ </Button>
35
+ </Dialog.Trigger>
36
+ );
37
+ };
38
+
39
+ ModalTrigger.displayName = "ModalTrigger";
@@ -0,0 +1,8 @@
1
+ export { ModalHeader } from "./ModalHeader";
2
+ export { ModalFooter } from "./ModalFooter";
3
+ export { ModalContent } from "./ModalContent";
4
+ 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";
@@ -0,0 +1,37 @@
1
+ // Modal - Explicit named exports for optimal tree shaking
2
+ export { default as Modal } from "./ModalV2";
3
+ export {
4
+ ModalHeader,
5
+ ModalFooter,
6
+ ModalContent,
7
+ ModalDescription,
8
+ ModalCloseButton,
9
+ ModalTrigger,
10
+ ModalClose,
11
+ ModalRail,
12
+ ModalAction,
13
+ } from "./components";
14
+
15
+ // Explicit type exports
16
+ export type {
17
+ TypeModalV2Props,
18
+ TypeModalV2TriggerProps,
19
+ TypeModalV2HeaderProps,
20
+ TypeModalV2FooterProps,
21
+ TypeModalV2ContentProps,
22
+ TypeModalV2DescriptionProps,
23
+ TypeModalV2CloseButtonProps,
24
+ TypeModalRailProps,
25
+ TypeModalActionProps,
26
+ } from "./ModalV2Types";
27
+
28
+ // Explicit constant exports
29
+ export {
30
+ DEFAULT_MODAL_Z_INDEX,
31
+ DEFAULT_OVERLAY_Z_INDEX_OFFSET,
32
+ DEFAULT_MODAL_WIDTH,
33
+ DEFAULT_MODAL_BG,
34
+ BODY_PADDING,
35
+ MODAL_SIZE_PRESETS,
36
+ MODAL_PRIORITY_Z_INDEX,
37
+ } from "../shared/constants";
package/tsconfig.json CHANGED
@@ -5,5 +5,11 @@
5
5
  "module": "esnext"
6
6
  },
7
7
  "include": ["src/**/*"],
8
- "exclude": ["node_modules", "dist", "coverage"]
8
+ "exclude": [
9
+ "node_modules",
10
+ "dist",
11
+ "coverage",
12
+ "**/*.stories.tsx",
13
+ "**/*.stories.ts"
14
+ ]
9
15
  }
package/tsup.config.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { defineConfig } from "tsup";
2
2
 
3
3
  export default defineConfig((options) => ({
4
- entry: ["src/index.ts"],
4
+ entry: {
5
+ index: "src/index.ts",
6
+ "v1/index": "src/v1/index.ts",
7
+ "v2/index": "src/v2/index.ts",
8
+ },
5
9
  format: ["cjs", "esm"],
6
10
  clean: true,
7
11
  legacyOutput: true,