@sproutsocial/seeds-react-content-block 0.4.0 → 0.5.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.
@@ -8,14 +8,14 @@ $ tsup --dts
8
8
  CLI Cleaning output folder
9
9
  CJS Build start
10
10
  ESM Build start
11
- CJS dist/index.js 4.32 KB
12
- CJS dist/index.js.map 5.44 KB
11
+ CJS dist/index.js 6.84 KB
12
+ CJS dist/index.js.map 8.58 KB
13
13
  CJS ⚡️ Build success in 13ms
14
- ESM dist/esm/index.js 2.67 KB
15
- ESM dist/esm/index.js.map 5.44 KB
16
- ESM ⚡️ Build success in 14ms
14
+ ESM dist/esm/index.js 4.37 KB
15
+ ESM dist/esm/index.js.map 8.56 KB
16
+ ESM ⚡️ Build success in 13ms
17
17
  DTS Build start
18
- DTS ⚡️ Build success in 1454ms
19
- DTS dist/index.d.ts 1.30 KB
20
- DTS dist/index.d.mts 1.30 KB
21
- Done in 2.21s.
18
+ DTS ⚡️ Build success in 1967ms
19
+ DTS dist/index.d.ts 1.51 KB
20
+ DTS dist/index.d.mts 1.51 KB
21
+ Done in 2.78s.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @sproutsocial/seeds-react-content-block
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - eb6937d: Add optional `footerContent` and `footerActions` props for a styled footer area below the content, and `flushLayout` boolean prop to set content area padding to 0
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [eb6937d]
12
+ - @sproutsocial/seeds-react-content-header@0.2.8
13
+
14
+ ## 0.4.1
15
+
16
+ ### Patch Changes
17
+
18
+ - @sproutsocial/seeds-react-button@2.0.2
19
+ - @sproutsocial/seeds-react-content-header@0.2.7
20
+
3
21
  ## 0.4.0
4
22
 
5
23
  ### Minor Changes
package/dist/esm/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/ContentBlock.tsx
2
2
  import { useState } from "react";
3
- import { Box } from "@sproutsocial/seeds-react-box";
3
+ import { Box as Box2 } from "@sproutsocial/seeds-react-box";
4
4
  import { Loader } from "@sproutsocial/seeds-react-loader";
5
5
  import { Collapsible } from "@sproutsocial/seeds-react-collapsible";
6
6
  import {
@@ -8,21 +8,51 @@ import {
8
8
  CollapsibleContentHeader
9
9
  } from "@sproutsocial/seeds-react-content-header";
10
10
  import { Container } from "@sproutsocial/seeds-react-container";
11
+
12
+ // src/styles.ts
13
+ import styled from "styled-components";
14
+ import { Box } from "@sproutsocial/seeds-react-box";
15
+ var ContentBlockContent = styled(Box)`
16
+ padding: ${({ theme }) => theme.space[400]};
17
+ `;
18
+ var FooterContainer = styled.div`
19
+ box-sizing: border-box;
20
+ display: flex;
21
+ justify-content: space-between;
22
+ align-items: center;
23
+ width: 100%;
24
+ padding: ${({ theme }) => theme.space[400]};
25
+ border-top: ${({ theme }) => theme.borderWidths[500]} solid
26
+ ${({ theme }) => theme.colors.container.border.base};
27
+ border-bottom-left-radius: ${({ theme }) => theme.radii.outer};
28
+ border-bottom-right-radius: ${({ theme }) => theme.radii.outer};
29
+ `;
30
+
31
+ // src/ContentBlock.tsx
11
32
  import { jsx, jsxs } from "react/jsx-runtime";
12
33
  var ContentArea = ({
13
34
  isLoading,
14
35
  children,
15
- contentProps
36
+ contentProps,
37
+ flushLayout,
38
+ hasFooter
16
39
  }) => /* @__PURE__ */ jsx(
17
- Box,
40
+ Box2,
18
41
  {
19
- p: 400,
42
+ p: flushLayout ? 0 : 400,
20
43
  ...contentProps,
21
- borderBottomLeftRadius: "outer",
22
- borderBottomRightRadius: "outer",
23
- children: isLoading ? /* @__PURE__ */ jsx(Box, { my: 400, children: /* @__PURE__ */ jsx(Loader, { delay: false }) }) : children
44
+ borderBottomLeftRadius: hasFooter ? void 0 : "outer",
45
+ borderBottomRightRadius: hasFooter ? void 0 : "outer",
46
+ children: isLoading ? /* @__PURE__ */ jsx(Box2, { my: 400, children: /* @__PURE__ */ jsx(Loader, { delay: false }) }) : children
24
47
  }
25
48
  );
49
+ var ContentFooter = ({
50
+ footerContent,
51
+ footerActions
52
+ }) => /* @__PURE__ */ jsxs(FooterContainer, { children: [
53
+ footerContent && /* @__PURE__ */ jsx("span", { children: footerContent }),
54
+ footerActions && /* @__PURE__ */ jsx("span", { children: footerActions })
55
+ ] });
26
56
  var CollapsibleContentBlock = ({
27
57
  title,
28
58
  subtitle,
@@ -32,12 +62,16 @@ var CollapsibleContentBlock = ({
32
62
  isLoading,
33
63
  headerActions,
34
64
  contentProps,
65
+ flushLayout,
66
+ footerContent,
67
+ footerActions,
35
68
  isOpen: controlledIsOpen,
36
69
  defaultOpen = true,
37
70
  onOpenChange
38
71
  }) => {
39
72
  const [internalOpen, setInternalOpen] = useState(defaultOpen);
40
73
  const open = controlledIsOpen !== void 0 ? controlledIsOpen : internalOpen;
74
+ const hasFooter = !!(footerContent || footerActions);
41
75
  const handleOpenChange = (newOpen) => {
42
76
  if (controlledIsOpen === void 0) {
43
77
  setInternalOpen(newOpen);
@@ -57,7 +91,25 @@ var CollapsibleContentBlock = ({
57
91
  triggerPosition: "left"
58
92
  }
59
93
  ),
60
- /* @__PURE__ */ jsx(Collapsible.Panel, { children: /* @__PURE__ */ jsx(ContentArea, { isLoading, contentProps, children }) })
94
+ /* @__PURE__ */ jsxs(Collapsible.Panel, { children: [
95
+ /* @__PURE__ */ jsx(
96
+ ContentArea,
97
+ {
98
+ isLoading,
99
+ contentProps,
100
+ flushLayout,
101
+ hasFooter,
102
+ children
103
+ }
104
+ ),
105
+ hasFooter && /* @__PURE__ */ jsx(
106
+ ContentFooter,
107
+ {
108
+ footerContent,
109
+ footerActions
110
+ }
111
+ )
112
+ ] })
61
113
  ] }) });
62
114
  };
63
115
  var ContentBlock = ({ isCollapsible, ...props }) => {
@@ -72,8 +124,12 @@ var ContentBlock = ({ isCollapsible, ...props }) => {
72
124
  children,
73
125
  isLoading,
74
126
  headerActions,
75
- contentProps
127
+ contentProps,
128
+ flushLayout,
129
+ footerContent,
130
+ footerActions
76
131
  } = props;
132
+ const hasFooter = !!(footerContent || footerActions);
77
133
  return /* @__PURE__ */ jsxs(Container, { as: "section", children: [
78
134
  /* @__PURE__ */ jsx(
79
135
  ContentHeader,
@@ -85,7 +141,23 @@ var ContentBlock = ({ isCollapsible, ...props }) => {
85
141
  headerActions
86
142
  }
87
143
  ),
88
- /* @__PURE__ */ jsx(ContentArea, { isLoading, contentProps, children })
144
+ /* @__PURE__ */ jsx(
145
+ ContentArea,
146
+ {
147
+ isLoading,
148
+ contentProps,
149
+ flushLayout,
150
+ hasFooter,
151
+ children
152
+ }
153
+ ),
154
+ hasFooter && /* @__PURE__ */ jsx(
155
+ ContentFooter,
156
+ {
157
+ footerContent,
158
+ footerActions
159
+ }
160
+ )
89
161
  ] });
90
162
  };
91
163
  var ContentBlock_default = ContentBlock;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ContentBlock.tsx","../../src/index.ts"],"sourcesContent":["import React, { useState } from \"react\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\nimport { Loader } from \"@sproutsocial/seeds-react-loader\";\nimport { Collapsible } from \"@sproutsocial/seeds-react-collapsible\";\nimport {\n ContentHeader,\n CollapsibleContentHeader,\n} from \"@sproutsocial/seeds-react-content-header\";\nimport { Container } from \"@sproutsocial/seeds-react-container\";\n\ninterface ContentBlockBaseProps {\n title: string;\n subtitle?: string;\n titleAs?: \"h1\" | \"h2\" | \"h3\" | \"h4\";\n subtitleAs?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"p\";\n isLoading?: boolean;\n children: React.ReactNode;\n headerActions?: React.ReactNode;\n contentProps?: React.ComponentProps<typeof Box>;\n}\n\ntype StaticContentBlockProps = ContentBlockBaseProps & {\n isCollapsible?: false;\n isOpen?: never;\n defaultOpen?: never;\n onOpenChange?: never;\n};\n\ntype CollapsibleContentBlockProps = ContentBlockBaseProps & {\n /** Enables collapse/expand behavior on the content area */\n isCollapsible: true;\n /** Controlled open state. When provided, the component is in controlled mode. */\n isOpen?: boolean;\n /** Initial open state for uncontrolled mode. Defaults to true. */\n defaultOpen?: boolean;\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n};\n\nexport type ContentBlockProps =\n | StaticContentBlockProps\n | CollapsibleContentBlockProps;\n\nconst ContentArea = ({\n isLoading,\n children,\n contentProps,\n}: Pick<ContentBlockBaseProps, \"isLoading\" | \"children\" | \"contentProps\">) => (\n <Box\n p={400}\n {...contentProps}\n borderBottomLeftRadius=\"outer\"\n borderBottomRightRadius=\"outer\"\n >\n {isLoading ? (\n <Box my={400}>\n <Loader delay={false} />\n </Box>\n ) : (\n children\n )}\n </Box>\n);\n\nconst CollapsibleContentBlock = ({\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n isOpen: controlledIsOpen,\n defaultOpen = true,\n onOpenChange,\n}: Omit<CollapsibleContentBlockProps, \"isCollapsible\">) => {\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = controlledIsOpen !== undefined ? controlledIsOpen : internalOpen;\n\n const handleOpenChange = (newOpen: boolean) => {\n if (controlledIsOpen === undefined) {\n setInternalOpen(newOpen);\n }\n onOpenChange?.(newOpen);\n };\n\n return (\n <Collapsible isOpen={open} onOpenChange={handleOpenChange}>\n <Container as=\"section\">\n <CollapsibleContentHeader\n title={title}\n subtitle={subtitle}\n headingLevel={titleAs}\n subtitleAs={subtitleAs}\n headerActions={headerActions}\n trigger={Collapsible.Trigger}\n triggerPosition=\"left\"\n />\n <Collapsible.Panel>\n <ContentArea isLoading={isLoading} contentProps={contentProps}>\n {children}\n </ContentArea>\n </Collapsible.Panel>\n </Container>\n </Collapsible>\n );\n};\n\nconst ContentBlock = ({ isCollapsible, ...props }: ContentBlockProps) => {\n if (isCollapsible) {\n return <CollapsibleContentBlock {...props} />;\n }\n\n const {\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n } = props;\n\n return (\n <Container as=\"section\">\n <ContentHeader\n title={title}\n subtitle={subtitle}\n headingLevel={titleAs}\n subtitleAs={subtitleAs}\n headerActions={headerActions}\n />\n <ContentArea isLoading={isLoading} contentProps={contentProps}>\n {children}\n </ContentArea>\n </Container>\n );\n};\n\nexport default ContentBlock;\n","import ContentBlock from \"./ContentBlock\";\n\nexport type { ContentBlockProps } from \"./ContentBlock\";\n\nexport default ContentBlock;\nexport { ContentBlock };\n"],"mappings":";AAAA,SAAgB,gBAAgB;AAChC,SAAS,WAAW;AACpB,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAgDlB,cAiCF,YAjCE;AAbR,IAAM,cAAc,CAAC;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,MACE;AAAA,EAAC;AAAA;AAAA,IACC,GAAG;AAAA,IACF,GAAG;AAAA,IACJ,wBAAuB;AAAA,IACvB,yBAAwB;AAAA,IAEvB,sBACC,oBAAC,OAAI,IAAI,KACP,8BAAC,UAAO,OAAO,OAAO,GACxB,IAEA;AAAA;AAEJ;AAGF,IAAM,0BAA0B,CAAC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,cAAc;AAAA,EACd;AACF,MAA2D;AACzD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,WAAW;AAC5D,QAAM,OAAO,qBAAqB,SAAY,mBAAmB;AAEjE,QAAM,mBAAmB,CAAC,YAAqB;AAC7C,QAAI,qBAAqB,QAAW;AAClC,sBAAgB,OAAO;AAAA,IACzB;AACA,mBAAe,OAAO;AAAA,EACxB;AAEA,SACE,oBAAC,eAAY,QAAQ,MAAM,cAAc,kBACvC,+BAAC,aAAU,IAAG,WACZ;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA;AAAA,QACA,SAAS,YAAY;AAAA,QACrB,iBAAgB;AAAA;AAAA,IAClB;AAAA,IACA,oBAAC,YAAY,OAAZ,EACC,8BAAC,eAAY,WAAsB,cAChC,UACH,GACF;AAAA,KACF,GACF;AAEJ;AAEA,IAAM,eAAe,CAAC,EAAE,eAAe,GAAG,MAAM,MAAyB;AACvE,MAAI,eAAe;AACjB,WAAO,oBAAC,2BAAyB,GAAG,OAAO;AAAA,EAC7C;AAEA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,SACE,qBAAC,aAAU,IAAG,WACZ;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IACA,oBAAC,eAAY,WAAsB,cAChC,UACH;AAAA,KACF;AAEJ;AAEA,IAAO,uBAAQ;;;ACzIf,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../src/ContentBlock.tsx","../../src/styles.ts","../../src/index.ts"],"sourcesContent":["import React, { useState } from \"react\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\nimport { Loader } from \"@sproutsocial/seeds-react-loader\";\nimport { Collapsible } from \"@sproutsocial/seeds-react-collapsible\";\nimport {\n ContentHeader,\n CollapsibleContentHeader,\n} from \"@sproutsocial/seeds-react-content-header\";\nimport { Container } from \"@sproutsocial/seeds-react-container\";\nimport { FooterContainer } from \"./styles\";\n\ninterface ContentBlockBaseProps {\n title: string;\n subtitle?: string;\n titleAs?: \"h1\" | \"h2\" | \"h3\" | \"h4\";\n subtitleAs?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"p\";\n isLoading?: boolean;\n children: React.ReactNode;\n headerActions?: React.ReactNode;\n contentProps?: React.ComponentProps<typeof Box>;\n /** Sets content area padding to 0 */\n flushLayout?: boolean;\n /** Left slot of the footer area */\n footerContent?: React.ReactNode;\n /** Right slot of the footer area */\n footerActions?: React.ReactNode;\n}\n\ntype StaticContentBlockProps = ContentBlockBaseProps & {\n isCollapsible?: false;\n isOpen?: never;\n defaultOpen?: never;\n onOpenChange?: never;\n};\n\ntype CollapsibleContentBlockProps = ContentBlockBaseProps & {\n /** Enables collapse/expand behavior on the content area */\n isCollapsible: true;\n /** Controlled open state. When provided, the component is in controlled mode. */\n isOpen?: boolean;\n /** Initial open state for uncontrolled mode. Defaults to true. */\n defaultOpen?: boolean;\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n};\n\nexport type ContentBlockProps =\n | StaticContentBlockProps\n | CollapsibleContentBlockProps;\n\nconst ContentArea = ({\n isLoading,\n children,\n contentProps,\n flushLayout,\n hasFooter,\n}: Pick<\n ContentBlockBaseProps,\n \"isLoading\" | \"children\" | \"contentProps\" | \"flushLayout\"\n> & { hasFooter?: boolean }) => (\n <Box\n p={flushLayout ? 0 : 400}\n {...contentProps}\n borderBottomLeftRadius={hasFooter ? undefined : \"outer\"}\n borderBottomRightRadius={hasFooter ? undefined : \"outer\"}\n >\n {isLoading ? (\n <Box my={400}>\n <Loader delay={false} />\n </Box>\n ) : (\n children\n )}\n </Box>\n);\n\nconst ContentFooter = ({\n footerContent,\n footerActions,\n}: {\n footerContent?: React.ReactNode;\n footerActions?: React.ReactNode;\n}) => (\n <FooterContainer>\n {footerContent && <span>{footerContent}</span>}\n {footerActions && <span>{footerActions}</span>}\n </FooterContainer>\n);\n\nconst CollapsibleContentBlock = ({\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n flushLayout,\n footerContent,\n footerActions,\n isOpen: controlledIsOpen,\n defaultOpen = true,\n onOpenChange,\n}: Omit<CollapsibleContentBlockProps, \"isCollapsible\">) => {\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = controlledIsOpen !== undefined ? controlledIsOpen : internalOpen;\n const hasFooter = !!(footerContent || footerActions);\n\n const handleOpenChange = (newOpen: boolean) => {\n if (controlledIsOpen === undefined) {\n setInternalOpen(newOpen);\n }\n onOpenChange?.(newOpen);\n };\n\n return (\n <Collapsible isOpen={open} onOpenChange={handleOpenChange}>\n <Container as=\"section\">\n <CollapsibleContentHeader\n title={title}\n subtitle={subtitle}\n headingLevel={titleAs}\n subtitleAs={subtitleAs}\n headerActions={headerActions}\n trigger={Collapsible.Trigger}\n triggerPosition=\"left\"\n />\n <Collapsible.Panel>\n <ContentArea\n isLoading={isLoading}\n contentProps={contentProps}\n flushLayout={flushLayout}\n hasFooter={hasFooter}\n >\n {children}\n </ContentArea>\n {hasFooter && (\n <ContentFooter\n footerContent={footerContent}\n footerActions={footerActions}\n />\n )}\n </Collapsible.Panel>\n </Container>\n </Collapsible>\n );\n};\n\nconst ContentBlock = ({ isCollapsible, ...props }: ContentBlockProps) => {\n if (isCollapsible) {\n return <CollapsibleContentBlock {...props} />;\n }\n\n const {\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n flushLayout,\n footerContent,\n footerActions,\n } = props;\n\n const hasFooter = !!(footerContent || footerActions);\n\n return (\n <Container as=\"section\">\n <ContentHeader\n title={title}\n subtitle={subtitle}\n headingLevel={titleAs}\n subtitleAs={subtitleAs}\n headerActions={headerActions}\n />\n <ContentArea\n isLoading={isLoading}\n contentProps={contentProps}\n flushLayout={flushLayout}\n hasFooter={hasFooter}\n >\n {children}\n </ContentArea>\n {hasFooter && (\n <ContentFooter\n footerContent={footerContent}\n footerActions={footerActions}\n />\n )}\n </Container>\n );\n};\n\nexport default ContentBlock;\n","import styled from \"styled-components\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\n\nexport const ContentBlockContent = styled(Box)`\n padding: ${({ theme }) => theme.space[400]};\n`;\n\nexport const FooterContainer = styled.div`\n box-sizing: border-box;\n display: flex;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n padding: ${({ theme }) => theme.space[400]};\n border-top: ${({ theme }) => theme.borderWidths[500]} solid\n ${({ theme }) => theme.colors.container.border.base};\n border-bottom-left-radius: ${({ theme }) => theme.radii.outer};\n border-bottom-right-radius: ${({ theme }) => theme.radii.outer};\n`;\n","import ContentBlock from \"./ContentBlock\";\n\nexport type { ContentBlockProps } from \"./ContentBlock\";\n\nexport default ContentBlock;\nexport { ContentBlock };\n"],"mappings":"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","names":["Box","Box"]}
package/dist/index.d.mts CHANGED
@@ -11,6 +11,12 @@ interface ContentBlockBaseProps {
11
11
  children: React.ReactNode;
12
12
  headerActions?: React.ReactNode;
13
13
  contentProps?: React.ComponentProps<typeof Box>;
14
+ /** Sets content area padding to 0 */
15
+ flushLayout?: boolean;
16
+ /** Left slot of the footer area */
17
+ footerContent?: React.ReactNode;
18
+ /** Right slot of the footer area */
19
+ footerActions?: React.ReactNode;
14
20
  }
15
21
  type StaticContentBlockProps = ContentBlockBaseProps & {
16
22
  isCollapsible?: false;
package/dist/index.d.ts CHANGED
@@ -11,6 +11,12 @@ interface ContentBlockBaseProps {
11
11
  children: React.ReactNode;
12
12
  headerActions?: React.ReactNode;
13
13
  contentProps?: React.ComponentProps<typeof Box>;
14
+ /** Sets content area padding to 0 */
15
+ flushLayout?: boolean;
16
+ /** Left slot of the footer area */
17
+ footerContent?: React.ReactNode;
18
+ /** Right slot of the footer area */
19
+ footerActions?: React.ReactNode;
14
20
  }
15
21
  type StaticContentBlockProps = ContentBlockBaseProps & {
16
22
  isCollapsible?: false;
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -27,26 +37,56 @@ module.exports = __toCommonJS(index_exports);
27
37
 
28
38
  // src/ContentBlock.tsx
29
39
  var import_react = require("react");
30
- var import_seeds_react_box = require("@sproutsocial/seeds-react-box");
40
+ var import_seeds_react_box2 = require("@sproutsocial/seeds-react-box");
31
41
  var import_seeds_react_loader = require("@sproutsocial/seeds-react-loader");
32
42
  var import_seeds_react_collapsible = require("@sproutsocial/seeds-react-collapsible");
33
43
  var import_seeds_react_content_header = require("@sproutsocial/seeds-react-content-header");
34
44
  var import_seeds_react_container = require("@sproutsocial/seeds-react-container");
45
+
46
+ // src/styles.ts
47
+ var import_styled_components = __toESM(require("styled-components"));
48
+ var import_seeds_react_box = require("@sproutsocial/seeds-react-box");
49
+ var ContentBlockContent = (0, import_styled_components.default)(import_seeds_react_box.Box)`
50
+ padding: ${({ theme }) => theme.space[400]};
51
+ `;
52
+ var FooterContainer = import_styled_components.default.div`
53
+ box-sizing: border-box;
54
+ display: flex;
55
+ justify-content: space-between;
56
+ align-items: center;
57
+ width: 100%;
58
+ padding: ${({ theme }) => theme.space[400]};
59
+ border-top: ${({ theme }) => theme.borderWidths[500]} solid
60
+ ${({ theme }) => theme.colors.container.border.base};
61
+ border-bottom-left-radius: ${({ theme }) => theme.radii.outer};
62
+ border-bottom-right-radius: ${({ theme }) => theme.radii.outer};
63
+ `;
64
+
65
+ // src/ContentBlock.tsx
35
66
  var import_jsx_runtime = require("react/jsx-runtime");
36
67
  var ContentArea = ({
37
68
  isLoading,
38
69
  children,
39
- contentProps
70
+ contentProps,
71
+ flushLayout,
72
+ hasFooter
40
73
  }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
41
- import_seeds_react_box.Box,
74
+ import_seeds_react_box2.Box,
42
75
  {
43
- p: 400,
76
+ p: flushLayout ? 0 : 400,
44
77
  ...contentProps,
45
- borderBottomLeftRadius: "outer",
46
- borderBottomRightRadius: "outer",
47
- children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_box.Box, { my: 400, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_loader.Loader, { delay: false }) }) : children
78
+ borderBottomLeftRadius: hasFooter ? void 0 : "outer",
79
+ borderBottomRightRadius: hasFooter ? void 0 : "outer",
80
+ children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_box2.Box, { my: 400, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_loader.Loader, { delay: false }) }) : children
48
81
  }
49
82
  );
83
+ var ContentFooter = ({
84
+ footerContent,
85
+ footerActions
86
+ }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(FooterContainer, { children: [
87
+ footerContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: footerContent }),
88
+ footerActions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: footerActions })
89
+ ] });
50
90
  var CollapsibleContentBlock = ({
51
91
  title,
52
92
  subtitle,
@@ -56,12 +96,16 @@ var CollapsibleContentBlock = ({
56
96
  isLoading,
57
97
  headerActions,
58
98
  contentProps,
99
+ flushLayout,
100
+ footerContent,
101
+ footerActions,
59
102
  isOpen: controlledIsOpen,
60
103
  defaultOpen = true,
61
104
  onOpenChange
62
105
  }) => {
63
106
  const [internalOpen, setInternalOpen] = (0, import_react.useState)(defaultOpen);
64
107
  const open = controlledIsOpen !== void 0 ? controlledIsOpen : internalOpen;
108
+ const hasFooter = !!(footerContent || footerActions);
65
109
  const handleOpenChange = (newOpen) => {
66
110
  if (controlledIsOpen === void 0) {
67
111
  setInternalOpen(newOpen);
@@ -81,7 +125,25 @@ var CollapsibleContentBlock = ({
81
125
  triggerPosition: "left"
82
126
  }
83
127
  ),
84
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_collapsible.Collapsible.Panel, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContentArea, { isLoading, contentProps, children }) })
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_seeds_react_collapsible.Collapsible.Panel, { children: [
129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
130
+ ContentArea,
131
+ {
132
+ isLoading,
133
+ contentProps,
134
+ flushLayout,
135
+ hasFooter,
136
+ children
137
+ }
138
+ ),
139
+ hasFooter && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
140
+ ContentFooter,
141
+ {
142
+ footerContent,
143
+ footerActions
144
+ }
145
+ )
146
+ ] })
85
147
  ] }) });
86
148
  };
87
149
  var ContentBlock = ({ isCollapsible, ...props }) => {
@@ -96,8 +158,12 @@ var ContentBlock = ({ isCollapsible, ...props }) => {
96
158
  children,
97
159
  isLoading,
98
160
  headerActions,
99
- contentProps
161
+ contentProps,
162
+ flushLayout,
163
+ footerContent,
164
+ footerActions
100
165
  } = props;
166
+ const hasFooter = !!(footerContent || footerActions);
101
167
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_seeds_react_container.Container, { as: "section", children: [
102
168
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
103
169
  import_seeds_react_content_header.ContentHeader,
@@ -109,7 +175,23 @@ var ContentBlock = ({ isCollapsible, ...props }) => {
109
175
  headerActions
110
176
  }
111
177
  ),
112
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContentArea, { isLoading, contentProps, children })
178
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
179
+ ContentArea,
180
+ {
181
+ isLoading,
182
+ contentProps,
183
+ flushLayout,
184
+ hasFooter,
185
+ children
186
+ }
187
+ ),
188
+ hasFooter && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
189
+ ContentFooter,
190
+ {
191
+ footerContent,
192
+ footerActions
193
+ }
194
+ )
113
195
  ] });
114
196
  };
115
197
  var ContentBlock_default = ContentBlock;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/ContentBlock.tsx"],"sourcesContent":["import ContentBlock from \"./ContentBlock\";\n\nexport type { ContentBlockProps } from \"./ContentBlock\";\n\nexport default ContentBlock;\nexport { ContentBlock };\n","import React, { useState } from \"react\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\nimport { Loader } from \"@sproutsocial/seeds-react-loader\";\nimport { Collapsible } from \"@sproutsocial/seeds-react-collapsible\";\nimport {\n ContentHeader,\n CollapsibleContentHeader,\n} from \"@sproutsocial/seeds-react-content-header\";\nimport { Container } from \"@sproutsocial/seeds-react-container\";\n\ninterface ContentBlockBaseProps {\n title: string;\n subtitle?: string;\n titleAs?: \"h1\" | \"h2\" | \"h3\" | \"h4\";\n subtitleAs?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"p\";\n isLoading?: boolean;\n children: React.ReactNode;\n headerActions?: React.ReactNode;\n contentProps?: React.ComponentProps<typeof Box>;\n}\n\ntype StaticContentBlockProps = ContentBlockBaseProps & {\n isCollapsible?: false;\n isOpen?: never;\n defaultOpen?: never;\n onOpenChange?: never;\n};\n\ntype CollapsibleContentBlockProps = ContentBlockBaseProps & {\n /** Enables collapse/expand behavior on the content area */\n isCollapsible: true;\n /** Controlled open state. When provided, the component is in controlled mode. */\n isOpen?: boolean;\n /** Initial open state for uncontrolled mode. Defaults to true. */\n defaultOpen?: boolean;\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n};\n\nexport type ContentBlockProps =\n | StaticContentBlockProps\n | CollapsibleContentBlockProps;\n\nconst ContentArea = ({\n isLoading,\n children,\n contentProps,\n}: Pick<ContentBlockBaseProps, \"isLoading\" | \"children\" | \"contentProps\">) => (\n <Box\n p={400}\n {...contentProps}\n borderBottomLeftRadius=\"outer\"\n borderBottomRightRadius=\"outer\"\n >\n {isLoading ? (\n <Box my={400}>\n <Loader delay={false} />\n </Box>\n ) : (\n children\n )}\n </Box>\n);\n\nconst CollapsibleContentBlock = ({\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n isOpen: controlledIsOpen,\n defaultOpen = true,\n onOpenChange,\n}: Omit<CollapsibleContentBlockProps, \"isCollapsible\">) => {\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = controlledIsOpen !== undefined ? controlledIsOpen : internalOpen;\n\n const handleOpenChange = (newOpen: boolean) => {\n if (controlledIsOpen === undefined) {\n setInternalOpen(newOpen);\n }\n onOpenChange?.(newOpen);\n };\n\n return (\n <Collapsible isOpen={open} onOpenChange={handleOpenChange}>\n <Container as=\"section\">\n <CollapsibleContentHeader\n title={title}\n subtitle={subtitle}\n headingLevel={titleAs}\n subtitleAs={subtitleAs}\n headerActions={headerActions}\n trigger={Collapsible.Trigger}\n triggerPosition=\"left\"\n />\n <Collapsible.Panel>\n <ContentArea isLoading={isLoading} contentProps={contentProps}>\n {children}\n </ContentArea>\n </Collapsible.Panel>\n </Container>\n </Collapsible>\n );\n};\n\nconst ContentBlock = ({ isCollapsible, ...props }: ContentBlockProps) => {\n if (isCollapsible) {\n return <CollapsibleContentBlock {...props} />;\n }\n\n const {\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n } = props;\n\n return (\n <Container as=\"section\">\n <ContentHeader\n title={title}\n subtitle={subtitle}\n headingLevel={titleAs}\n subtitleAs={subtitleAs}\n headerActions={headerActions}\n />\n <ContentArea isLoading={isLoading} contentProps={contentProps}>\n {children}\n </ContentArea>\n </Container>\n );\n};\n\nexport default ContentBlock;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAgC;AAChC,6BAAoB;AACpB,gCAAuB;AACvB,qCAA4B;AAC5B,wCAGO;AACP,mCAA0B;AAgDlB;AAbR,IAAM,cAAc,CAAC;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,MACE;AAAA,EAAC;AAAA;AAAA,IACC,GAAG;AAAA,IACF,GAAG;AAAA,IACJ,wBAAuB;AAAA,IACvB,yBAAwB;AAAA,IAEvB,sBACC,4CAAC,8BAAI,IAAI,KACP,sDAAC,oCAAO,OAAO,OAAO,GACxB,IAEA;AAAA;AAEJ;AAGF,IAAM,0BAA0B,CAAC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,cAAc;AAAA,EACd;AACF,MAA2D;AACzD,QAAM,CAAC,cAAc,eAAe,QAAI,uBAAS,WAAW;AAC5D,QAAM,OAAO,qBAAqB,SAAY,mBAAmB;AAEjE,QAAM,mBAAmB,CAAC,YAAqB;AAC7C,QAAI,qBAAqB,QAAW;AAClC,sBAAgB,OAAO;AAAA,IACzB;AACA,mBAAe,OAAO;AAAA,EACxB;AAEA,SACE,4CAAC,8CAAY,QAAQ,MAAM,cAAc,kBACvC,uDAAC,0CAAU,IAAG,WACZ;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA;AAAA,QACA,SAAS,2CAAY;AAAA,QACrB,iBAAgB;AAAA;AAAA,IAClB;AAAA,IACA,4CAAC,2CAAY,OAAZ,EACC,sDAAC,eAAY,WAAsB,cAChC,UACH,GACF;AAAA,KACF,GACF;AAEJ;AAEA,IAAM,eAAe,CAAC,EAAE,eAAe,GAAG,MAAM,MAAyB;AACvE,MAAI,eAAe;AACjB,WAAO,4CAAC,2BAAyB,GAAG,OAAO;AAAA,EAC7C;AAEA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,SACE,6CAAC,0CAAU,IAAG,WACZ;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IACA,4CAAC,eAAY,WAAsB,cAChC,UACH;AAAA,KACF;AAEJ;AAEA,IAAO,uBAAQ;;;ADzIf,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/ContentBlock.tsx","../src/styles.ts"],"sourcesContent":["import ContentBlock from \"./ContentBlock\";\n\nexport type { ContentBlockProps } from \"./ContentBlock\";\n\nexport default ContentBlock;\nexport { ContentBlock };\n","import React, { useState } from \"react\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\nimport { Loader } from \"@sproutsocial/seeds-react-loader\";\nimport { Collapsible } from \"@sproutsocial/seeds-react-collapsible\";\nimport {\n ContentHeader,\n CollapsibleContentHeader,\n} from \"@sproutsocial/seeds-react-content-header\";\nimport { Container } from \"@sproutsocial/seeds-react-container\";\nimport { FooterContainer } from \"./styles\";\n\ninterface ContentBlockBaseProps {\n title: string;\n subtitle?: string;\n titleAs?: \"h1\" | \"h2\" | \"h3\" | \"h4\";\n subtitleAs?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"p\";\n isLoading?: boolean;\n children: React.ReactNode;\n headerActions?: React.ReactNode;\n contentProps?: React.ComponentProps<typeof Box>;\n /** Sets content area padding to 0 */\n flushLayout?: boolean;\n /** Left slot of the footer area */\n footerContent?: React.ReactNode;\n /** Right slot of the footer area */\n footerActions?: React.ReactNode;\n}\n\ntype StaticContentBlockProps = ContentBlockBaseProps & {\n isCollapsible?: false;\n isOpen?: never;\n defaultOpen?: never;\n onOpenChange?: never;\n};\n\ntype CollapsibleContentBlockProps = ContentBlockBaseProps & {\n /** Enables collapse/expand behavior on the content area */\n isCollapsible: true;\n /** Controlled open state. When provided, the component is in controlled mode. */\n isOpen?: boolean;\n /** Initial open state for uncontrolled mode. Defaults to true. */\n defaultOpen?: boolean;\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n};\n\nexport type ContentBlockProps =\n | StaticContentBlockProps\n | CollapsibleContentBlockProps;\n\nconst ContentArea = ({\n isLoading,\n children,\n contentProps,\n flushLayout,\n hasFooter,\n}: Pick<\n ContentBlockBaseProps,\n \"isLoading\" | \"children\" | \"contentProps\" | \"flushLayout\"\n> & { hasFooter?: boolean }) => (\n <Box\n p={flushLayout ? 0 : 400}\n {...contentProps}\n borderBottomLeftRadius={hasFooter ? undefined : \"outer\"}\n borderBottomRightRadius={hasFooter ? undefined : \"outer\"}\n >\n {isLoading ? (\n <Box my={400}>\n <Loader delay={false} />\n </Box>\n ) : (\n children\n )}\n </Box>\n);\n\nconst ContentFooter = ({\n footerContent,\n footerActions,\n}: {\n footerContent?: React.ReactNode;\n footerActions?: React.ReactNode;\n}) => (\n <FooterContainer>\n {footerContent && <span>{footerContent}</span>}\n {footerActions && <span>{footerActions}</span>}\n </FooterContainer>\n);\n\nconst CollapsibleContentBlock = ({\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n flushLayout,\n footerContent,\n footerActions,\n isOpen: controlledIsOpen,\n defaultOpen = true,\n onOpenChange,\n}: Omit<CollapsibleContentBlockProps, \"isCollapsible\">) => {\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = controlledIsOpen !== undefined ? controlledIsOpen : internalOpen;\n const hasFooter = !!(footerContent || footerActions);\n\n const handleOpenChange = (newOpen: boolean) => {\n if (controlledIsOpen === undefined) {\n setInternalOpen(newOpen);\n }\n onOpenChange?.(newOpen);\n };\n\n return (\n <Collapsible isOpen={open} onOpenChange={handleOpenChange}>\n <Container as=\"section\">\n <CollapsibleContentHeader\n title={title}\n subtitle={subtitle}\n headingLevel={titleAs}\n subtitleAs={subtitleAs}\n headerActions={headerActions}\n trigger={Collapsible.Trigger}\n triggerPosition=\"left\"\n />\n <Collapsible.Panel>\n <ContentArea\n isLoading={isLoading}\n contentProps={contentProps}\n flushLayout={flushLayout}\n hasFooter={hasFooter}\n >\n {children}\n </ContentArea>\n {hasFooter && (\n <ContentFooter\n footerContent={footerContent}\n footerActions={footerActions}\n />\n )}\n </Collapsible.Panel>\n </Container>\n </Collapsible>\n );\n};\n\nconst ContentBlock = ({ isCollapsible, ...props }: ContentBlockProps) => {\n if (isCollapsible) {\n return <CollapsibleContentBlock {...props} />;\n }\n\n const {\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n flushLayout,\n footerContent,\n footerActions,\n } = props;\n\n const hasFooter = !!(footerContent || footerActions);\n\n return (\n <Container as=\"section\">\n <ContentHeader\n title={title}\n subtitle={subtitle}\n headingLevel={titleAs}\n subtitleAs={subtitleAs}\n headerActions={headerActions}\n />\n <ContentArea\n isLoading={isLoading}\n contentProps={contentProps}\n flushLayout={flushLayout}\n hasFooter={hasFooter}\n >\n {children}\n </ContentArea>\n {hasFooter && (\n <ContentFooter\n footerContent={footerContent}\n footerActions={footerActions}\n />\n )}\n </Container>\n );\n};\n\nexport default ContentBlock;\n","import styled from \"styled-components\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\n\nexport const ContentBlockContent = styled(Box)`\n padding: ${({ theme }) => theme.space[400]};\n`;\n\nexport const FooterContainer = styled.div`\n box-sizing: border-box;\n display: flex;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n padding: ${({ theme }) => theme.space[400]};\n border-top: ${({ theme }) => theme.borderWidths[500]} solid\n ${({ theme }) => theme.colors.container.border.base};\n border-bottom-left-radius: ${({ theme }) => theme.radii.outer};\n border-bottom-right-radius: ${({ theme }) => theme.radii.outer};\n`;\n"],"mappings":"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","names":["import_seeds_react_box","styled"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-content-block",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Seeds React ContentBlock",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -19,10 +19,10 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@sproutsocial/seeds-react-box": "^1.1.16",
22
- "@sproutsocial/seeds-react-button": "^2.0.1",
22
+ "@sproutsocial/seeds-react-button": "^2.0.2",
23
23
  "@sproutsocial/seeds-react-collapsible": "^2.0.0",
24
24
  "@sproutsocial/seeds-react-container": "^0.3.10",
25
- "@sproutsocial/seeds-react-content-header": "^0.2.6",
25
+ "@sproutsocial/seeds-react-content-header": "^0.2.8",
26
26
  "@sproutsocial/seeds-react-loader": "^1.0.16",
27
27
  "@sproutsocial/seeds-react-theme": "^3.6.2"
28
28
  },
@@ -2,6 +2,7 @@ import React, { useState } from "react";
2
2
  import ContentBlock from "./ContentBlock";
3
3
  import type { Meta, StoryObj } from "@storybook/react";
4
4
  import { Text } from "@sproutsocial/seeds-react-text";
5
+ import { Box } from "@sproutsocial/seeds-react-box";
5
6
  import { Button } from "@sproutsocial/seeds-react-button";
6
7
  import { Icon } from "@sproutsocial/seeds-react-icon";
7
8
 
@@ -56,6 +57,45 @@ export const WithContentStyles: Story = {
56
57
  ),
57
58
  };
58
59
 
60
+ export const WithFooter: Story = {
61
+ render: (args) => (
62
+ <ContentBlock
63
+ {...args}
64
+ footerContent={
65
+ <Text.SmallBodyCopy>Footer description text</Text.SmallBodyCopy>
66
+ }
67
+ footerActions={
68
+ <Button
69
+ href="https://app.sproutsocial.com/dashboard/"
70
+ appearance="secondary"
71
+ >
72
+ See profile performance
73
+ </Button>
74
+ }
75
+ />
76
+ ),
77
+ };
78
+
79
+ export const WithFlushContent: Story = {
80
+ render: (args) => (
81
+ <ContentBlock
82
+ {...args}
83
+ flushLayout
84
+ footerContent={
85
+ <Text.SmallBodyCopy>Footer description text</Text.SmallBodyCopy>
86
+ }
87
+ >
88
+ <Box p={400}>Impressions</Box>
89
+ <Box p={400} borderTop={500} borderColor="container.border.base">
90
+ Engagements
91
+ </Box>
92
+ <Box p={400} borderTop={500} borderColor="container.border.base">
93
+ Followers Gained
94
+ </Box>
95
+ </ContentBlock>
96
+ ),
97
+ };
98
+
59
99
  export const Collapsible: Story = {
60
100
  render: (args) => (
61
101
  <ContentBlock {...args} isCollapsible>
@@ -98,6 +138,31 @@ export const CollapsibleWithHeaderActions: Story = {
98
138
  ),
99
139
  };
100
140
 
141
+ export const CollapsibleWithFooter: Story = {
142
+ name: "Collapsible with Footer",
143
+ render: (args) => (
144
+ <ContentBlock
145
+ {...args}
146
+ isCollapsible
147
+ footerContent={
148
+ <Text.SmallBodyCopy>Footer description text</Text.SmallBodyCopy>
149
+ }
150
+ footerActions={
151
+ <Button
152
+ href="https://app.sproutsocial.com/dashboard/"
153
+ appearance="secondary"
154
+ >
155
+ See profile performance
156
+ </Button>
157
+ }
158
+ >
159
+ <Text.SmallBodyCopy>
160
+ The footer collapses along with the content.
161
+ </Text.SmallBodyCopy>
162
+ </ContentBlock>
163
+ ),
164
+ };
165
+
101
166
  export const CollapsibleControlled: Story = {
102
167
  name: "Collapsible (Controlled)",
103
168
  render: (args) => {
@@ -7,6 +7,7 @@ import {
7
7
  CollapsibleContentHeader,
8
8
  } from "@sproutsocial/seeds-react-content-header";
9
9
  import { Container } from "@sproutsocial/seeds-react-container";
10
+ import { FooterContainer } from "./styles";
10
11
 
11
12
  interface ContentBlockBaseProps {
12
13
  title: string;
@@ -17,6 +18,12 @@ interface ContentBlockBaseProps {
17
18
  children: React.ReactNode;
18
19
  headerActions?: React.ReactNode;
19
20
  contentProps?: React.ComponentProps<typeof Box>;
21
+ /** Sets content area padding to 0 */
22
+ flushLayout?: boolean;
23
+ /** Left slot of the footer area */
24
+ footerContent?: React.ReactNode;
25
+ /** Right slot of the footer area */
26
+ footerActions?: React.ReactNode;
20
27
  }
21
28
 
22
29
  type StaticContentBlockProps = ContentBlockBaseProps & {
@@ -45,12 +52,17 @@ const ContentArea = ({
45
52
  isLoading,
46
53
  children,
47
54
  contentProps,
48
- }: Pick<ContentBlockBaseProps, "isLoading" | "children" | "contentProps">) => (
55
+ flushLayout,
56
+ hasFooter,
57
+ }: Pick<
58
+ ContentBlockBaseProps,
59
+ "isLoading" | "children" | "contentProps" | "flushLayout"
60
+ > & { hasFooter?: boolean }) => (
49
61
  <Box
50
- p={400}
62
+ p={flushLayout ? 0 : 400}
51
63
  {...contentProps}
52
- borderBottomLeftRadius="outer"
53
- borderBottomRightRadius="outer"
64
+ borderBottomLeftRadius={hasFooter ? undefined : "outer"}
65
+ borderBottomRightRadius={hasFooter ? undefined : "outer"}
54
66
  >
55
67
  {isLoading ? (
56
68
  <Box my={400}>
@@ -62,6 +74,19 @@ const ContentArea = ({
62
74
  </Box>
63
75
  );
64
76
 
77
+ const ContentFooter = ({
78
+ footerContent,
79
+ footerActions,
80
+ }: {
81
+ footerContent?: React.ReactNode;
82
+ footerActions?: React.ReactNode;
83
+ }) => (
84
+ <FooterContainer>
85
+ {footerContent && <span>{footerContent}</span>}
86
+ {footerActions && <span>{footerActions}</span>}
87
+ </FooterContainer>
88
+ );
89
+
65
90
  const CollapsibleContentBlock = ({
66
91
  title,
67
92
  subtitle,
@@ -71,12 +96,16 @@ const CollapsibleContentBlock = ({
71
96
  isLoading,
72
97
  headerActions,
73
98
  contentProps,
99
+ flushLayout,
100
+ footerContent,
101
+ footerActions,
74
102
  isOpen: controlledIsOpen,
75
103
  defaultOpen = true,
76
104
  onOpenChange,
77
105
  }: Omit<CollapsibleContentBlockProps, "isCollapsible">) => {
78
106
  const [internalOpen, setInternalOpen] = useState(defaultOpen);
79
107
  const open = controlledIsOpen !== undefined ? controlledIsOpen : internalOpen;
108
+ const hasFooter = !!(footerContent || footerActions);
80
109
 
81
110
  const handleOpenChange = (newOpen: boolean) => {
82
111
  if (controlledIsOpen === undefined) {
@@ -98,9 +127,20 @@ const CollapsibleContentBlock = ({
98
127
  triggerPosition="left"
99
128
  />
100
129
  <Collapsible.Panel>
101
- <ContentArea isLoading={isLoading} contentProps={contentProps}>
130
+ <ContentArea
131
+ isLoading={isLoading}
132
+ contentProps={contentProps}
133
+ flushLayout={flushLayout}
134
+ hasFooter={hasFooter}
135
+ >
102
136
  {children}
103
137
  </ContentArea>
138
+ {hasFooter && (
139
+ <ContentFooter
140
+ footerContent={footerContent}
141
+ footerActions={footerActions}
142
+ />
143
+ )}
104
144
  </Collapsible.Panel>
105
145
  </Container>
106
146
  </Collapsible>
@@ -121,8 +161,13 @@ const ContentBlock = ({ isCollapsible, ...props }: ContentBlockProps) => {
121
161
  isLoading,
122
162
  headerActions,
123
163
  contentProps,
164
+ flushLayout,
165
+ footerContent,
166
+ footerActions,
124
167
  } = props;
125
168
 
169
+ const hasFooter = !!(footerContent || footerActions);
170
+
126
171
  return (
127
172
  <Container as="section">
128
173
  <ContentHeader
@@ -132,9 +177,20 @@ const ContentBlock = ({ isCollapsible, ...props }: ContentBlockProps) => {
132
177
  subtitleAs={subtitleAs}
133
178
  headerActions={headerActions}
134
179
  />
135
- <ContentArea isLoading={isLoading} contentProps={contentProps}>
180
+ <ContentArea
181
+ isLoading={isLoading}
182
+ contentProps={contentProps}
183
+ flushLayout={flushLayout}
184
+ hasFooter={hasFooter}
185
+ >
136
186
  {children}
137
187
  </ContentArea>
188
+ {hasFooter && (
189
+ <ContentFooter
190
+ footerContent={footerContent}
191
+ footerActions={footerActions}
192
+ />
193
+ )}
138
194
  </Container>
139
195
  );
140
196
  };
@@ -73,6 +73,65 @@ describe("ContentBlock Features", () => {
73
73
  });
74
74
  });
75
75
 
76
+ describe("ContentBlock Footer", () => {
77
+ test("renders footerContent when provided", () => {
78
+ render(
79
+ <ContentBlock title="Title" footerContent={<span>Footer text</span>}>
80
+ Content
81
+ </ContentBlock>
82
+ );
83
+ expect(screen.getByText("Footer text")).toBeInTheDocument();
84
+ });
85
+
86
+ test("renders footerActions when provided", () => {
87
+ render(
88
+ <ContentBlock
89
+ title="Title"
90
+ footerActions={<button>Footer action</button>}
91
+ >
92
+ Content
93
+ </ContentBlock>
94
+ );
95
+ expect(screen.getByText("Footer action")).toBeInTheDocument();
96
+ });
97
+
98
+ test("renders both footerContent and footerActions together", () => {
99
+ render(
100
+ <ContentBlock
101
+ title="Title"
102
+ footerContent={<span>Footer text</span>}
103
+ footerActions={<button>Footer action</button>}
104
+ >
105
+ Content
106
+ </ContentBlock>
107
+ );
108
+ expect(screen.getByText("Footer text")).toBeInTheDocument();
109
+ expect(screen.getByText("Footer action")).toBeInTheDocument();
110
+ });
111
+
112
+ test("does not render footer when neither prop is provided", () => {
113
+ render(<ContentBlock title="Title">Content</ContentBlock>);
114
+ expect(screen.queryByText("Footer text")).not.toBeInTheDocument();
115
+ });
116
+
117
+ test("collapsible footer is visible when open and hidden when collapsed", () => {
118
+ render(
119
+ <ContentBlock
120
+ title="Title"
121
+ isCollapsible
122
+ footerContent={<span>Footer text</span>}
123
+ >
124
+ Content
125
+ </ContentBlock>
126
+ );
127
+
128
+ expect(screen.getByText("Footer text")).toBeInTheDocument();
129
+
130
+ fireEvent.click(screen.getByRole("button"));
131
+ expect(screen.queryByText("Footer text")).not.toBeInTheDocument();
132
+ });
133
+ });
134
+
76
135
  describe("ContentBlock Collapsible", () => {
77
136
  test("does not render chevron when not collapsible", () => {
78
137
  render(
package/src/styles.ts CHANGED
@@ -4,3 +4,16 @@ import { Box } from "@sproutsocial/seeds-react-box";
4
4
  export const ContentBlockContent = styled(Box)`
5
5
  padding: ${({ theme }) => theme.space[400]};
6
6
  `;
7
+
8
+ export const FooterContainer = styled.div`
9
+ box-sizing: border-box;
10
+ display: flex;
11
+ justify-content: space-between;
12
+ align-items: center;
13
+ width: 100%;
14
+ padding: ${({ theme }) => theme.space[400]};
15
+ border-top: ${({ theme }) => theme.borderWidths[500]} solid
16
+ ${({ theme }) => theme.colors.container.border.base};
17
+ border-bottom-left-radius: ${({ theme }) => theme.radii.outer};
18
+ border-bottom-right-radius: ${({ theme }) => theme.radii.outer};
19
+ `;