@sproutsocial/seeds-react-modal 1.0.1 → 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.
- package/.turbo/turbo-build.log +39 -19
- package/CHANGELOG.md +14 -0
- package/dist/Modal-ki8oiGbC.d.mts +69 -0
- package/dist/Modal-ki8oiGbC.d.ts +69 -0
- package/dist/ModalRail-OQ8DZ1vH.d.mts +178 -0
- package/dist/ModalRail-OQ8DZ1vH.d.ts +178 -0
- package/dist/esm/chunk-GKQRFPCX.js +642 -0
- package/dist/esm/chunk-GKQRFPCX.js.map +1 -0
- package/dist/esm/chunk-IYDY4OPB.js +237 -0
- package/dist/esm/chunk-IYDY4OPB.js.map +1 -0
- package/dist/esm/index.js +28 -231
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/v1/index.js +9 -0
- package/dist/esm/v1/index.js.map +1 -0
- package/dist/esm/v2/index.js +39 -0
- package/dist/esm/v2/index.js.map +1 -0
- package/dist/index.d.mts +11 -73
- package/dist/index.d.ts +11 -73
- package/dist/index.js +670 -25
- package/dist/index.js.map +1 -1
- package/dist/v1/index.d.mts +11 -0
- package/dist/v1/index.d.ts +11 -0
- package/dist/v1/index.js +273 -0
- package/dist/v1/index.js.map +1 -0
- package/dist/v2/index.d.mts +26 -0
- package/dist/v2/index.d.ts +26 -0
- package/dist/v2/index.js +694 -0
- package/dist/v2/index.js.map +1 -0
- package/package.json +33 -13
- package/src/Modal.stories.tsx +3 -6
- package/src/Modal.tsx +12 -13
- package/src/__tests__/v1/Modal.test.tsx +134 -0
- package/src/__tests__/v1/Modal.typetest.tsx +209 -0
- package/src/index.ts +36 -3
- package/src/shared/constants.ts +28 -0
- package/src/v1/Modal.tsx +159 -0
- package/src/v1/ModalTypes.ts +67 -0
- package/src/v1/index.ts +14 -0
- package/src/v1/styles.tsx +141 -0
- package/src/v2/ModalV2.stories.tsx +282 -0
- package/src/v2/ModalV2.tsx +306 -0
- package/src/v2/ModalV2Styles.tsx +150 -0
- package/src/v2/ModalV2Types.ts +158 -0
- package/src/v2/components/ModalClose.tsx +29 -0
- package/src/v2/components/ModalCloseButton.tsx +100 -0
- package/src/v2/components/ModalContent.tsx +16 -0
- package/src/v2/components/ModalDescription.tsx +19 -0
- package/src/v2/components/ModalFooter.tsx +20 -0
- package/src/v2/components/ModalHeader.tsx +52 -0
- package/src/v2/components/ModalRail.tsx +121 -0
- package/src/v2/components/ModalTrigger.tsx +39 -0
- package/src/v2/components/index.ts +8 -0
- package/src/v2/index.ts +37 -0
- package/tsconfig.json +7 -1
- 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";
|
package/src/v2/index.ts
ADDED
|
@@ -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
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:
|
|
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,
|