@sproutsocial/seeds-react-message 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.
- package/.eslintignore +6 -0
- package/.eslintrc.js +4 -0
- package/.turbo/turbo-build.log +21 -0
- package/CHANGELOG.md +7 -0
- package/dist/esm/index.js +888 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.mts +73 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +919 -0
- package/dist/index.js.map +1 -0
- package/jest.config.js +9 -0
- package/package.json +45 -0
- package/src/Message.stories.tsx +75 -0
- package/src/Message.tsx +177 -0
- package/src/MessageTypes.ts +23 -0
- package/src/__tests__/Message.typetest.tsx +57 -0
- package/src/__tests__/features.test.tsx +10 -0
- package/src/constants.ts +7 -0
- package/src/index.ts +6 -0
- package/src/styled.d.ts +7 -0
- package/src/styles.ts +139 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +12 -0
package/src/Message.tsx
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import Avatar, { type TypeAvatarProps } from "@sproutsocial/seeds-react-avatar";
|
|
3
|
+
import Button, { type TypeButtonProps } from "@sproutsocial/seeds-react-button";
|
|
4
|
+
import Checkbox, {
|
|
5
|
+
type TypeCheckboxProps,
|
|
6
|
+
} from "@sproutsocial/seeds-react-checkbox";
|
|
7
|
+
import Container, {
|
|
8
|
+
MessageBody as StyledMessageBody,
|
|
9
|
+
MessageFooter as StyledMessageFooter,
|
|
10
|
+
MessageHeader as StyledMessageHeader,
|
|
11
|
+
MessageMeta as StyledMessageMeta,
|
|
12
|
+
} from "./styles";
|
|
13
|
+
import { MESSAGE_DENSITIES } from "./constants";
|
|
14
|
+
import type { EnumDensities, TypeMessageProps } from "./MessageTypes";
|
|
15
|
+
|
|
16
|
+
const avatarSizeMap = (density: EnumDensities) => {
|
|
17
|
+
switch (density) {
|
|
18
|
+
case MESSAGE_DENSITIES.LARGE:
|
|
19
|
+
return "40px";
|
|
20
|
+
|
|
21
|
+
case MESSAGE_DENSITIES.COMPACT:
|
|
22
|
+
return "20px";
|
|
23
|
+
|
|
24
|
+
case MESSAGE_DENSITIES.CONDENSED:
|
|
25
|
+
return "24px";
|
|
26
|
+
|
|
27
|
+
case MESSAGE_DENSITIES.SMALL:
|
|
28
|
+
default:
|
|
29
|
+
return "32px";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const MessageContext = React.createContext<{
|
|
34
|
+
density: EnumDensities;
|
|
35
|
+
borderColor: TypeMessageProps["borderColor"];
|
|
36
|
+
bg?: TypeMessageProps["bg"];
|
|
37
|
+
indentContent?: boolean;
|
|
38
|
+
}>({
|
|
39
|
+
density: MESSAGE_DENSITIES.SMALL,
|
|
40
|
+
borderColor: "container.border.base",
|
|
41
|
+
bg: "container.background.base",
|
|
42
|
+
indentContent: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const Message = ({
|
|
46
|
+
children,
|
|
47
|
+
density = MESSAGE_DENSITIES.SMALL,
|
|
48
|
+
borderColor = "container.border.base",
|
|
49
|
+
bg = "container.background.base",
|
|
50
|
+
indentContent = true,
|
|
51
|
+
innerRef,
|
|
52
|
+
qa,
|
|
53
|
+
...rest
|
|
54
|
+
}: TypeMessageProps) => {
|
|
55
|
+
return (
|
|
56
|
+
<MessageContext.Provider
|
|
57
|
+
value={{
|
|
58
|
+
density,
|
|
59
|
+
borderColor,
|
|
60
|
+
indentContent,
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<Container
|
|
64
|
+
borderColor={borderColor}
|
|
65
|
+
bg={bg}
|
|
66
|
+
ref={innerRef}
|
|
67
|
+
tabIndex={0}
|
|
68
|
+
{...qa}
|
|
69
|
+
{...rest}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</Container>
|
|
73
|
+
</MessageContext.Provider>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const MessageBody = (props: TypeMessageProps) => (
|
|
78
|
+
<MessageContext.Consumer>
|
|
79
|
+
{({ density, borderColor, indentContent }) => (
|
|
80
|
+
<StyledMessageBody
|
|
81
|
+
data-qa-message-body
|
|
82
|
+
density={density}
|
|
83
|
+
borderColor={borderColor}
|
|
84
|
+
indentContent={indentContent}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
88
|
+
</MessageContext.Consumer>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
MessageBody.displayName = "Message.Body";
|
|
92
|
+
Message.Body = MessageBody;
|
|
93
|
+
|
|
94
|
+
const MessageHeader = (props: TypeMessageProps) => (
|
|
95
|
+
<MessageContext.Consumer>
|
|
96
|
+
{({ density, borderColor }) => (
|
|
97
|
+
<StyledMessageHeader
|
|
98
|
+
density={density}
|
|
99
|
+
borderColor={borderColor}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
</MessageContext.Consumer>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
MessageHeader.displayName = "Message.Header";
|
|
107
|
+
Message.Header = MessageHeader;
|
|
108
|
+
|
|
109
|
+
const MessageFooter = (props: TypeMessageProps) => (
|
|
110
|
+
<MessageContext.Consumer>
|
|
111
|
+
{({ density, borderColor, indentContent }) => (
|
|
112
|
+
<StyledMessageFooter
|
|
113
|
+
density={density}
|
|
114
|
+
borderColor={borderColor}
|
|
115
|
+
indentContent={indentContent}
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
)}
|
|
119
|
+
</MessageContext.Consumer>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
MessageFooter.displayName = "Message.Footer";
|
|
123
|
+
Message.Footer = MessageFooter;
|
|
124
|
+
|
|
125
|
+
const MessageMeta = (props: TypeMessageProps) => (
|
|
126
|
+
<MessageContext.Consumer>
|
|
127
|
+
{({ density, borderColor, indentContent }) => (
|
|
128
|
+
<StyledMessageMeta
|
|
129
|
+
density={density}
|
|
130
|
+
borderColor={borderColor}
|
|
131
|
+
indentContent={indentContent}
|
|
132
|
+
{...props}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
</MessageContext.Consumer>
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
MessageMeta.displayName = "Message.Meta";
|
|
139
|
+
Message.Meta = MessageMeta;
|
|
140
|
+
|
|
141
|
+
const MessageButton = (props: TypeButtonProps) => (
|
|
142
|
+
<Button {...props} appearance={props.appearance || "pill"} />
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
MessageButton.displayName = "Message.Button";
|
|
146
|
+
Message.Button = MessageButton;
|
|
147
|
+
|
|
148
|
+
const MessageAvatar = (props: TypeAvatarProps) => (
|
|
149
|
+
<MessageContext.Consumer>
|
|
150
|
+
{({ density }) => <Avatar size={avatarSizeMap(density)} {...props} />}
|
|
151
|
+
</MessageContext.Consumer>
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
MessageAvatar.displayName = "Message.Avatar";
|
|
155
|
+
Message.Avatar = MessageAvatar;
|
|
156
|
+
|
|
157
|
+
const MessageCheckbox = (props: TypeCheckboxProps) => (
|
|
158
|
+
<MessageContext.Consumer>
|
|
159
|
+
{({ density }) => (
|
|
160
|
+
<Checkbox
|
|
161
|
+
appearance="pill"
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
163
|
+
// @ts-ignore - `density` is not a valid prop for `Checkbox`
|
|
164
|
+
density={density} // @ts-notes - not sure what's intended here so address during refactor
|
|
165
|
+
{...props}
|
|
166
|
+
/>
|
|
167
|
+
)}
|
|
168
|
+
</MessageContext.Consumer>
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
MessageCheckbox.displayName = "Message.Checkbox";
|
|
172
|
+
Message.Checkbox = MessageCheckbox;
|
|
173
|
+
|
|
174
|
+
Message.Context = MessageContext;
|
|
175
|
+
Message.DENSITIES = MESSAGE_DENSITIES;
|
|
176
|
+
|
|
177
|
+
export default Message;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { TypeBoxProps } from "@sproutsocial/seeds-react-box";
|
|
3
|
+
import { MESSAGE_DENSITIES } from "./constants";
|
|
4
|
+
|
|
5
|
+
type TypeQaProps = object;
|
|
6
|
+
|
|
7
|
+
/* @ts-notes - this is a little hack to get the values of the const object,
|
|
8
|
+
but this should be updated to use an enum instead during the refactor */
|
|
9
|
+
type TypeDensityKeys = keyof typeof MESSAGE_DENSITIES;
|
|
10
|
+
export type EnumDensities = (typeof MESSAGE_DENSITIES)[TypeDensityKeys];
|
|
11
|
+
|
|
12
|
+
export interface TypeMessageProps extends TypeBoxProps {
|
|
13
|
+
/** Condensed, small, or large. */
|
|
14
|
+
density?: EnumDensities;
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
innerRef?: React.Ref<HTMLDivElement>;
|
|
17
|
+
borderColor?: string;
|
|
18
|
+
// seeds borderColor tokens (enum when flow types are available)
|
|
19
|
+
bg?: string;
|
|
20
|
+
// seeds color tokens (enum when flow types are available)
|
|
21
|
+
indentContent?: boolean;
|
|
22
|
+
qa?: TypeQaProps;
|
|
23
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render } from "@sproutsocial/seeds-react-testing-library";
|
|
3
|
+
import { Box } from "@sproutsocial/seeds-react-box";
|
|
4
|
+
import { Icon } from "@sproutsocial/seeds-react-icon";
|
|
5
|
+
import Message from "../Message";
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
8
|
+
function MessageTypes() {
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<Message density="condensed" bg="container.background.base">
|
|
12
|
+
<Message.Header>
|
|
13
|
+
<Box display="flex" alignItems="center">
|
|
14
|
+
<Message.Avatar mr={350} appearance="leaf" name="Chase McCoy" />
|
|
15
|
+
Attribution
|
|
16
|
+
</Box>
|
|
17
|
+
<Box>
|
|
18
|
+
<Message.Checkbox
|
|
19
|
+
id="message-checkbox"
|
|
20
|
+
ariaLabel="Message Checkbox"
|
|
21
|
+
onChange={jest.fn()}
|
|
22
|
+
/>
|
|
23
|
+
</Box>
|
|
24
|
+
</Message.Header>
|
|
25
|
+
<Message.Body>Message Content</Message.Body>
|
|
26
|
+
<Message.Footer>
|
|
27
|
+
<>
|
|
28
|
+
<Box>
|
|
29
|
+
<Message.Button>
|
|
30
|
+
<Icon name="comments-outline" mr={200} />
|
|
31
|
+
button with text
|
|
32
|
+
</Message.Button>
|
|
33
|
+
</Box>
|
|
34
|
+
<Box>
|
|
35
|
+
<Message.Button aria-label="Heart Icon Example">
|
|
36
|
+
<Icon name="heart-outline" />
|
|
37
|
+
</Message.Button>
|
|
38
|
+
<Message.Button aria-label="Tag Icon Example">
|
|
39
|
+
<Icon name="tag-outline" />
|
|
40
|
+
</Message.Button>
|
|
41
|
+
<Message.Button aria-label="Reply Icon Example">
|
|
42
|
+
<Icon name="reply-outline" />
|
|
43
|
+
</Message.Button>
|
|
44
|
+
<Message.Button aria-label="Check Icon Example">
|
|
45
|
+
<Icon name="circle-check-outline" />
|
|
46
|
+
</Message.Button>
|
|
47
|
+
</Box>
|
|
48
|
+
</>
|
|
49
|
+
</Message.Footer>
|
|
50
|
+
</Message>
|
|
51
|
+
{/* @ts-expect-error - test that missing required props is rejected */}
|
|
52
|
+
<Message />
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render(<MessageTypes />);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { render } from "@sproutsocial/seeds-react-testing-library";
|
|
2
|
+
import { defaultStory as defaultMessageStory } from "../Message.stories";
|
|
3
|
+
|
|
4
|
+
describe("Message", () => {
|
|
5
|
+
it("should render properly", async () => {
|
|
6
|
+
const { container, runA11yCheck } = render(defaultMessageStory({}));
|
|
7
|
+
expect(container).toBeTruthy();
|
|
8
|
+
await runA11yCheck();
|
|
9
|
+
});
|
|
10
|
+
});
|
package/src/constants.ts
ADDED
package/src/index.ts
ADDED
package/src/styled.d.ts
ADDED
package/src/styles.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { memo } from "react";
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
import { focusRing } from "@sproutsocial/seeds-react-mixins";
|
|
4
|
+
import Box from "@sproutsocial/seeds-react-box";
|
|
5
|
+
import type { TypeTheme } from "@sproutsocial/seeds-react-theme";
|
|
6
|
+
import type { TypeMessageProps } from "./MessageTypes";
|
|
7
|
+
import { MESSAGE_DENSITIES } from "./constants";
|
|
8
|
+
|
|
9
|
+
const Container = styled(Box)`
|
|
10
|
+
border-radius: ${(props) => props.theme.radii.outer};
|
|
11
|
+
border-width: ${(props) => props.theme.borderWidths[500]};
|
|
12
|
+
border-style: solid;
|
|
13
|
+
background-color: ${(props) => {
|
|
14
|
+
//@ts-ignore
|
|
15
|
+
return props.theme.colors[props.bg];
|
|
16
|
+
}};
|
|
17
|
+
&:focus {
|
|
18
|
+
${focusRing} transition: box-shadow 0.15s;
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
Container.displayName = "Message.Container";
|
|
23
|
+
|
|
24
|
+
export const MessageHeader = memo(styled(Box)<TypeMessageProps>`
|
|
25
|
+
padding: ${(props) =>
|
|
26
|
+
props.density === MESSAGE_DENSITIES.CONDENSED
|
|
27
|
+
? `0 0 0 ${props.theme.space[100]}`
|
|
28
|
+
: props.theme.space[300]};
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
align-items: center;
|
|
32
|
+
border-bottom-width: ${(props) =>
|
|
33
|
+
props.density === MESSAGE_DENSITIES.CONDENSED
|
|
34
|
+
? 0
|
|
35
|
+
: props.theme.borderWidths[500]};
|
|
36
|
+
border-bottom-style: solid;
|
|
37
|
+
border-radius: ${(props) => props.theme.radii.outer}
|
|
38
|
+
${(props) => props.theme.radii.outer} 0 0;
|
|
39
|
+
${(props) => props.theme.typography[200]};
|
|
40
|
+
`);
|
|
41
|
+
|
|
42
|
+
const getContentPadding = (props: TypeMessageProps & { theme: TypeTheme }) => {
|
|
43
|
+
if (!props.indentContent) {
|
|
44
|
+
return props.theme.space[400];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
switch (props.density) {
|
|
48
|
+
case MESSAGE_DENSITIES.LARGE: {
|
|
49
|
+
// 40 px avatar + 8px(message.header) + 16px(mr)
|
|
50
|
+
return "64px";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case MESSAGE_DENSITIES.COMPACT: {
|
|
54
|
+
// 20px avatar plus space.300 margin plus space.400 margin
|
|
55
|
+
return "40px";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case MESSAGE_DENSITIES.CONDENSED: {
|
|
59
|
+
// 24px avatar plus space.200 margin plus space.350 margin
|
|
60
|
+
return "40px";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case MESSAGE_DENSITIES.SMALL:
|
|
64
|
+
default: {
|
|
65
|
+
// 32px avatar plus space.300 margin plus space.400 margin
|
|
66
|
+
return "56px";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const messagePadding = ({
|
|
72
|
+
density,
|
|
73
|
+
theme,
|
|
74
|
+
}: {
|
|
75
|
+
density?: TypeMessageProps["density"];
|
|
76
|
+
theme: TypeTheme;
|
|
77
|
+
}) => {
|
|
78
|
+
if (density === MESSAGE_DENSITIES.CONDENSED) {
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (density === MESSAGE_DENSITIES.COMPACT) {
|
|
83
|
+
return theme.space[300];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return theme.space[400];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const MessageBody = styled(Box)<TypeMessageProps>`
|
|
90
|
+
padding: ${messagePadding} ${messagePadding} ${messagePadding}
|
|
91
|
+
${(props) => getContentPadding(props)};
|
|
92
|
+
font-family: ${(props) => props.theme.fontFamily};
|
|
93
|
+
${(props) =>
|
|
94
|
+
props.density === MESSAGE_DENSITIES.COMPACT ||
|
|
95
|
+
props.density === MESSAGE_DENSITIES.CONDENSED
|
|
96
|
+
? props.theme.typography[200]
|
|
97
|
+
: props.theme.typography[300]};
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
export const MessageFooter = styled(Box)<TypeMessageProps>`
|
|
101
|
+
padding-bottom: ${(props) =>
|
|
102
|
+
props.density === MESSAGE_DENSITIES.CONDENSED ? 0 : props.theme.space[300]};
|
|
103
|
+
padding-left: ${(props) => getContentPadding(props)};
|
|
104
|
+
padding-right: ${(props) =>
|
|
105
|
+
props.density === MESSAGE_DENSITIES.CONDENSED ? 0 : props.theme.space[300]};
|
|
106
|
+
border-radius: 0 0 ${(props) => props.theme.radii.outer}
|
|
107
|
+
${(props) => props.theme.radii.outer};
|
|
108
|
+
display: flex;
|
|
109
|
+
justify-content: space-between;
|
|
110
|
+
align-items: center;
|
|
111
|
+
flex-wrap: ${(props) =>
|
|
112
|
+
props.density === MESSAGE_DENSITIES.CONDENSED ? "nowrap" : "wrap"};
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
export const MessageMeta = styled(Box)<TypeMessageProps>`
|
|
116
|
+
background: none;
|
|
117
|
+
border-width: 0;
|
|
118
|
+
border-style: none;
|
|
119
|
+
border-top-width: 1px;
|
|
120
|
+
border-top-style: solid;
|
|
121
|
+
display: block;
|
|
122
|
+
width: 100%;
|
|
123
|
+
text-align: start;
|
|
124
|
+
border-radius: 0 0 ${(props) => props.theme.radii.outer}
|
|
125
|
+
${(props) => props.theme.radii.outer};
|
|
126
|
+
${(props) => props.theme.typography[100]};
|
|
127
|
+
margin: ${(props) => props.theme.space[300]} 0
|
|
128
|
+
${(props) => props.theme.space[200]};
|
|
129
|
+
padding: ${(props) => props.theme.space[200]}
|
|
130
|
+
${(props) => props.theme.space[400]};
|
|
131
|
+
padding-left: ${(props) => getContentPadding(props)};
|
|
132
|
+
cursor: ${(props) => (props.onClick ? "pointer" : "default")};
|
|
133
|
+
color: ${(props) => props.theme.colors.text.subtext};
|
|
134
|
+
&:focus {
|
|
135
|
+
${focusRing};
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
export default Container;
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig((options) => ({
|
|
4
|
+
entry: ["src/index.ts"],
|
|
5
|
+
format: ["cjs", "esm"],
|
|
6
|
+
clean: true,
|
|
7
|
+
legacyOutput: true,
|
|
8
|
+
dts: options.dts,
|
|
9
|
+
external: ["react"],
|
|
10
|
+
sourcemap: true,
|
|
11
|
+
metafile: options.metafile,
|
|
12
|
+
}));
|