@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.
@@ -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
+ });
@@ -0,0 +1,7 @@
1
+ export const MESSAGE_DENSITIES = {
2
+ COMPACT: "compact",
3
+ // Compact is deprecated. New applications should use condensed instead.
4
+ CONDENSED: "condensed",
5
+ SMALL: "small",
6
+ LARGE: "large",
7
+ } as const;
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ import Message from "./Message";
2
+
3
+ export default Message;
4
+ export { Message };
5
+ export * from "./MessageTypes";
6
+ export * from "./constants";
@@ -0,0 +1,7 @@
1
+ import "styled-components";
2
+ import { TypeTheme } from "@sproutsocial/seeds-react-theme";
3
+
4
+ declare module "styled-components" {
5
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
6
+ export interface DefaultTheme extends TypeTheme {}
7
+ }
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
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@sproutsocial/seeds-tsconfig/bundler/dom/library-monorepo",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "module": "esnext"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "coverage"]
9
+ }
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
+ }));