@sproutsocial/seeds-react-content-block 0.2.13 → 0.3.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.
@@ -3,19 +3,19 @@ $ tsup --dts
3
3
  CLI Building entry: src/index.ts
4
4
  CLI Using tsconfig: tsconfig.json
5
5
  CLI tsup v8.5.0
6
- CLI Using tsup config: /home/runner/work/seeds/seeds/seeds-react/seeds-react-content-block/tsup.config.ts
6
+ CLI Using tsup config: /home/runner/_work/seeds/seeds/seeds-react/seeds-react-content-block/tsup.config.ts
7
7
  CLI Target: es2022
8
8
  CLI Cleaning output folder
9
9
  CJS Build start
10
10
  ESM Build start
11
- CJS dist/index.js 4.24 KB
12
- CJS dist/index.js.map 3.56 KB
13
- CJS ⚡️ Build success in 211ms
14
- ESM dist/esm/index.js 2.02 KB
15
- ESM dist/esm/index.js.map 3.52 KB
16
- ESM ⚡️ Build success in 200ms
11
+ CJS dist/index.js 4.32 KB
12
+ CJS dist/index.js.map 5.44 KB
13
+ CJS ⚡️ Build success in 11ms
14
+ ESM dist/esm/index.js 2.67 KB
15
+ ESM dist/esm/index.js.map 5.44 KB
16
+ ESM ⚡️ Build success in 12ms
17
17
  DTS Build start
18
- DTS ⚡️ Build success in 29684ms
19
- DTS dist/index.d.ts 521.00 B
20
- DTS dist/index.d.mts 521.00 B
21
- Done in 38.38s.
18
+ DTS ⚡️ Build success in 1398ms
19
+ DTS dist/index.d.ts 1.29 KB
20
+ DTS dist/index.d.mts 1.29 KB
21
+ Done in 2.13s.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # @sproutsocial/seeds-react-content-block
2
2
 
3
+ ## 0.3.3
4
+
5
+ ### Patch Changes
6
+
7
+ - @sproutsocial/seeds-react-button@1.4.1
8
+ - @sproutsocial/seeds-react-content-header@0.2.2
9
+
10
+ ## 0.3.2
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [7d54d67]
15
+ - @sproutsocial/seeds-react-button@1.4.0
16
+
17
+ ## 0.3.1
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [17d4f12]
22
+ - @sproutsocial/seeds-react-theme@3.6.0
23
+ - @sproutsocial/seeds-react-box@1.1.14
24
+ - @sproutsocial/seeds-react-container@0.3.8
25
+ - @sproutsocial/seeds-react-loader@1.0.14
26
+ - @sproutsocial/seeds-react-content-header@0.2.1
27
+ - @sproutsocial/seeds-react-button@1.3.20
28
+
29
+ ## 0.3.0
30
+
31
+ ### Minor Changes
32
+
33
+ - 5bb63e1: ContentBlock now uses ContentHeader/CollapsibleContentHeader internally and supports optional collapsible behavior via isCollapsible prop.
34
+
35
+ ### Patch Changes
36
+
37
+ - Updated dependencies [5bb63e1]
38
+ - Updated dependencies [5bb63e1]
39
+ - @sproutsocial/seeds-react-content-header@0.2.0
40
+ - @sproutsocial/seeds-react-container@0.3.7
41
+ - @sproutsocial/seeds-react-button@1.3.19
42
+ - @sproutsocial/seeds-react-loader@1.0.13
43
+
3
44
  ## 0.2.13
4
45
 
5
46
  ### Patch Changes
package/dist/esm/index.js CHANGED
@@ -1,29 +1,29 @@
1
1
  // src/ContentBlock.tsx
2
- import "react";
3
- import { Box as Box2 } from "@sproutsocial/seeds-react-box";
4
- import { Text } from "@sproutsocial/seeds-react-text";
5
- import { Loader } from "@sproutsocial/seeds-react-loader";
6
-
7
- // src/styles.ts
8
- import styled from "styled-components";
2
+ import { useState } from "react";
9
3
  import { Box } from "@sproutsocial/seeds-react-box";
10
- var ContentBlockHeader = styled(Box)`
11
- display: flex;
12
- justify-content: space-between;
13
- align-items: center;
14
- border-bottom: ${({ theme }) => theme.borderWidths[500]} solid
15
- ${({ theme }) => theme.colors.container.border.base};
16
- color: ${({ theme }) => theme.colors.text.headline};
17
- padding: ${({ theme }) => theme.space[400]};
18
- `;
19
- var ContentBlockContent = styled(Box)`
20
- padding: ${({ theme }) => theme.space[400]};
21
- `;
22
-
23
- // src/ContentBlock.tsx
4
+ import { Loader } from "@sproutsocial/seeds-react-loader";
5
+ import { Collapsible } from "@sproutsocial/seeds-react-collapsible";
6
+ import {
7
+ ContentHeader,
8
+ CollapsibleContentHeader
9
+ } from "@sproutsocial/seeds-react-content-header";
24
10
  import { Container } from "@sproutsocial/seeds-react-container";
25
11
  import { jsx, jsxs } from "react/jsx-runtime";
26
- var ContentBlock = ({
12
+ var ContentArea = ({
13
+ isLoading,
14
+ children,
15
+ contentProps
16
+ }) => /* @__PURE__ */ jsx(
17
+ Box,
18
+ {
19
+ p: 400,
20
+ ...contentProps,
21
+ borderBottomLeftRadius: "outer",
22
+ borderBottomRightRadius: "outer",
23
+ children: isLoading ? /* @__PURE__ */ jsx(Box, { my: 400, children: /* @__PURE__ */ jsx(Loader, { delay: false }) }) : children
24
+ }
25
+ );
26
+ var CollapsibleContentBlock = ({
27
27
  title,
28
28
  subtitle,
29
29
  titleAs = "h2",
@@ -32,26 +32,60 @@ var ContentBlock = ({
32
32
  isLoading,
33
33
  headerActions,
34
34
  contentProps,
35
- ...boxProps
35
+ isOpen: controlledIsOpen,
36
+ defaultOpen = true,
37
+ onOpenChange
36
38
  }) => {
37
- return /* @__PURE__ */ jsxs(Container, { as: "section", ...boxProps, children: [
38
- /* @__PURE__ */ jsxs(ContentBlockHeader, { children: [
39
- /* @__PURE__ */ jsxs(Box2, { children: [
40
- /* @__PURE__ */ jsx(Text.Headline, { as: titleAs, children: title }),
41
- /* @__PURE__ */ jsx(Text.Byline, { as: subtitleAs, fontWeight: "normal", children: subtitle })
42
- ] }),
43
- headerActions && /* @__PURE__ */ jsx(Box2, { children: headerActions })
44
- ] }),
39
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
40
+ const open = controlledIsOpen !== void 0 ? controlledIsOpen : internalOpen;
41
+ const handleOpenChange = (newOpen) => {
42
+ if (controlledIsOpen === void 0) {
43
+ setInternalOpen(newOpen);
44
+ }
45
+ onOpenChange?.(newOpen);
46
+ };
47
+ return /* @__PURE__ */ jsx(Collapsible, { isOpen: open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(Container, { as: "section", children: [
48
+ /* @__PURE__ */ jsx(
49
+ CollapsibleContentHeader,
50
+ {
51
+ title,
52
+ subtitle,
53
+ headingLevel: titleAs,
54
+ subtitleAs,
55
+ headerActions,
56
+ trigger: Collapsible.Trigger,
57
+ triggerPosition: "left"
58
+ }
59
+ ),
60
+ /* @__PURE__ */ jsx(Collapsible.Panel, { children: /* @__PURE__ */ jsx(ContentArea, { isLoading, contentProps, children }) })
61
+ ] }) });
62
+ };
63
+ var ContentBlock = ({ isCollapsible, ...props }) => {
64
+ if (isCollapsible) {
65
+ return /* @__PURE__ */ jsx(CollapsibleContentBlock, { ...props });
66
+ }
67
+ const {
68
+ title,
69
+ subtitle,
70
+ titleAs = "h2",
71
+ subtitleAs = "h3",
72
+ children,
73
+ isLoading,
74
+ headerActions,
75
+ contentProps
76
+ } = props;
77
+ return /* @__PURE__ */ jsxs(Container, { as: "section", children: [
45
78
  /* @__PURE__ */ jsx(
46
- Box2,
79
+ ContentHeader,
47
80
  {
48
- p: 400,
49
- ...contentProps,
50
- borderBottomLeftRadius: "outer",
51
- borderBottomRightRadius: "outer",
52
- children: isLoading ? /* @__PURE__ */ jsx(Box2, { my: 400, children: /* @__PURE__ */ jsx(Loader, { delay: false }) }) : children
81
+ title,
82
+ subtitle,
83
+ headingLevel: titleAs,
84
+ subtitleAs,
85
+ headerActions
53
86
  }
54
- )
87
+ ),
88
+ /* @__PURE__ */ jsx(ContentArea, { isLoading, contentProps, children })
55
89
  ] });
56
90
  };
57
91
  var ContentBlock_default = ContentBlock;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ContentBlock.tsx","../../src/styles.ts","../../src/index.ts"],"sourcesContent":["import React from \"react\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\nimport { Text } from \"@sproutsocial/seeds-react-text\";\nimport { Loader } from \"@sproutsocial/seeds-react-loader\";\nimport { ContentBlockHeader } from \"./styles\";\nimport { Container } from \"@sproutsocial/seeds-react-container\";\n\nexport interface ContentBlockProps {\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\nconst ContentBlock: React.FC<ContentBlockProps> = ({\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n ...boxProps\n}) => {\n return (\n <Container as=\"section\" {...boxProps}>\n <ContentBlockHeader>\n <Box>\n <Text.Headline as={titleAs}>{title}</Text.Headline>\n <Text.Byline as={subtitleAs} fontWeight=\"normal\">\n {subtitle}\n </Text.Byline>\n </Box>\n {headerActions && <Box>{headerActions}</Box>}\n </ContentBlockHeader>\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 </Container>\n );\n};\n\nexport default ContentBlock;\n","import styled from \"styled-components\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\n\nexport const ContentBlockHeader = styled(Box)`\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: ${({ theme }) => theme.borderWidths[500]} solid\n ${({ theme }) => theme.colors.container.border.base};\n color: ${({ theme }) => theme.colors.text.headline};\n padding: ${({ theme }) => theme.space[400]};\n`;\n\nexport const ContentBlockContent = styled(Box)`\n padding: ${({ theme }) => theme.space[400]};\n`;\n","import ContentBlock from \"./ContentBlock\";\n\nexport type { ContentBlockProps } from \"./ContentBlock\";\n\nexport default ContentBlock;\nexport { ContentBlock };\n"],"mappings":";AAAA,OAAkB;AAClB,SAAS,OAAAA,YAAW;AACpB,SAAS,YAAY;AACrB,SAAS,cAAc;;;ACHvB,OAAO,YAAY;AACnB,SAAS,WAAW;AAEb,IAAM,qBAAqB,OAAO,GAAG;AAAA;AAAA;AAAA;AAAA,mBAIzB,CAAC,EAAE,MAAM,MAAM,MAAM,aAAa,GAAG,CAAC;AAAA,MACnD,CAAC,EAAE,MAAM,MAAM,MAAM,OAAO,UAAU,OAAO,IAAI;AAAA,WAC5C,CAAC,EAAE,MAAM,MAAM,MAAM,OAAO,KAAK,QAAQ;AAAA,aACvC,CAAC,EAAE,MAAM,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA;AAGrC,IAAM,sBAAsB,OAAO,GAAG;AAAA,aAChC,CAAC,EAAE,MAAM,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA;;;ADT5C,SAAS,iBAAiB;AA2BlB,SACE,KADF;AAdR,IAAM,eAA4C,CAAC;AAAA,EACjD;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAM;AACJ,SACE,qBAAC,aAAU,IAAG,WAAW,GAAG,UAC1B;AAAA,yBAAC,sBACC;AAAA,2BAACC,MAAA,EACC;AAAA,4BAAC,KAAK,UAAL,EAAc,IAAI,SAAU,iBAAM;AAAA,QACnC,oBAAC,KAAK,QAAL,EAAY,IAAI,YAAY,YAAW,UACrC,oBACH;AAAA,SACF;AAAA,MACC,iBAAiB,oBAACA,MAAA,EAAK,yBAAc;AAAA,OACxC;AAAA,IACA;AAAA,MAACA;AAAA,MAAA;AAAA,QACC,GAAG;AAAA,QACF,GAAG;AAAA,QACJ,wBAAuB;AAAA,QACvB,yBAAwB;AAAA,QAEvB,sBACC,oBAACA,MAAA,EAAI,IAAI,KACP,8BAAC,UAAO,OAAO,OAAO,GACxB,IAEA;AAAA;AAAA,IAEJ;AAAA,KACF;AAEJ;AAEA,IAAO,uBAAQ;;;AEtDf,IAAO,gBAAQ;","names":["Box","Box"]}
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":[]}
package/dist/index.d.mts CHANGED
@@ -1,16 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
1
2
  import React from 'react';
2
3
  import { Box } from '@sproutsocial/seeds-react-box';
3
4
 
4
- interface ContentBlockProps {
5
+ interface ContentBlockBaseProps {
5
6
  title: string;
6
7
  subtitle: string;
7
- titleAs: "h1" | "h2" | "h3" | "h4";
8
- subtitleAs: "h2" | "h3" | "h4" | "h5" | "p";
8
+ titleAs?: "h1" | "h2" | "h3" | "h4";
9
+ subtitleAs?: "h2" | "h3" | "h4" | "h5" | "p";
9
10
  isLoading?: boolean;
10
11
  children: React.ReactNode;
11
12
  headerActions?: React.ReactNode;
12
13
  contentProps?: React.ComponentProps<typeof Box>;
13
14
  }
14
- declare const ContentBlock: React.FC<ContentBlockProps>;
15
+ type StaticContentBlockProps = ContentBlockBaseProps & {
16
+ isCollapsible?: false;
17
+ isOpen?: never;
18
+ defaultOpen?: never;
19
+ onOpenChange?: never;
20
+ };
21
+ type CollapsibleContentBlockProps = ContentBlockBaseProps & {
22
+ /** Enables collapse/expand behavior on the content area */
23
+ isCollapsible: true;
24
+ /** Controlled open state. When provided, the component is in controlled mode. */
25
+ isOpen?: boolean;
26
+ /** Initial open state for uncontrolled mode. Defaults to true. */
27
+ defaultOpen?: boolean;
28
+ /** Called when the open state changes */
29
+ onOpenChange?: (open: boolean) => void;
30
+ };
31
+ type ContentBlockProps = StaticContentBlockProps | CollapsibleContentBlockProps;
32
+ declare const ContentBlock: ({ isCollapsible, ...props }: ContentBlockProps) => react_jsx_runtime.JSX.Element;
15
33
 
16
34
  export { ContentBlock, type ContentBlockProps, ContentBlock as default };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
1
2
  import React from 'react';
2
3
  import { Box } from '@sproutsocial/seeds-react-box';
3
4
 
4
- interface ContentBlockProps {
5
+ interface ContentBlockBaseProps {
5
6
  title: string;
6
7
  subtitle: string;
7
- titleAs: "h1" | "h2" | "h3" | "h4";
8
- subtitleAs: "h2" | "h3" | "h4" | "h5" | "p";
8
+ titleAs?: "h1" | "h2" | "h3" | "h4";
9
+ subtitleAs?: "h2" | "h3" | "h4" | "h5" | "p";
9
10
  isLoading?: boolean;
10
11
  children: React.ReactNode;
11
12
  headerActions?: React.ReactNode;
12
13
  contentProps?: React.ComponentProps<typeof Box>;
13
14
  }
14
- declare const ContentBlock: React.FC<ContentBlockProps>;
15
+ type StaticContentBlockProps = ContentBlockBaseProps & {
16
+ isCollapsible?: false;
17
+ isOpen?: never;
18
+ defaultOpen?: never;
19
+ onOpenChange?: never;
20
+ };
21
+ type CollapsibleContentBlockProps = ContentBlockBaseProps & {
22
+ /** Enables collapse/expand behavior on the content area */
23
+ isCollapsible: true;
24
+ /** Controlled open state. When provided, the component is in controlled mode. */
25
+ isOpen?: boolean;
26
+ /** Initial open state for uncontrolled mode. Defaults to true. */
27
+ defaultOpen?: boolean;
28
+ /** Called when the open state changes */
29
+ onOpenChange?: (open: boolean) => void;
30
+ };
31
+ type ContentBlockProps = StaticContentBlockProps | CollapsibleContentBlockProps;
32
+ declare const ContentBlock: ({ isCollapsible, ...props }: ContentBlockProps) => react_jsx_runtime.JSX.Element;
15
33
 
16
34
  export { ContentBlock, type ContentBlockProps, ContentBlock as default };
package/dist/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
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
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
@@ -37,30 +27,27 @@ module.exports = __toCommonJS(index_exports);
37
27
 
38
28
  // src/ContentBlock.tsx
39
29
  var import_react = require("react");
40
- var import_seeds_react_box2 = require("@sproutsocial/seeds-react-box");
41
- var import_seeds_react_text = require("@sproutsocial/seeds-react-text");
42
- var import_seeds_react_loader = require("@sproutsocial/seeds-react-loader");
43
-
44
- // src/styles.ts
45
- var import_styled_components = __toESM(require("styled-components"));
46
30
  var import_seeds_react_box = require("@sproutsocial/seeds-react-box");
47
- var ContentBlockHeader = (0, import_styled_components.default)(import_seeds_react_box.Box)`
48
- display: flex;
49
- justify-content: space-between;
50
- align-items: center;
51
- border-bottom: ${({ theme }) => theme.borderWidths[500]} solid
52
- ${({ theme }) => theme.colors.container.border.base};
53
- color: ${({ theme }) => theme.colors.text.headline};
54
- padding: ${({ theme }) => theme.space[400]};
55
- `;
56
- var ContentBlockContent = (0, import_styled_components.default)(import_seeds_react_box.Box)`
57
- padding: ${({ theme }) => theme.space[400]};
58
- `;
59
-
60
- // src/ContentBlock.tsx
31
+ var import_seeds_react_loader = require("@sproutsocial/seeds-react-loader");
32
+ var import_seeds_react_collapsible = require("@sproutsocial/seeds-react-collapsible");
33
+ var import_seeds_react_content_header = require("@sproutsocial/seeds-react-content-header");
61
34
  var import_seeds_react_container = require("@sproutsocial/seeds-react-container");
62
35
  var import_jsx_runtime = require("react/jsx-runtime");
63
- var ContentBlock = ({
36
+ var ContentArea = ({
37
+ isLoading,
38
+ children,
39
+ contentProps
40
+ }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
41
+ import_seeds_react_box.Box,
42
+ {
43
+ p: 400,
44
+ ...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
48
+ }
49
+ );
50
+ var CollapsibleContentBlock = ({
64
51
  title,
65
52
  subtitle,
66
53
  titleAs = "h2",
@@ -69,26 +56,60 @@ var ContentBlock = ({
69
56
  isLoading,
70
57
  headerActions,
71
58
  contentProps,
72
- ...boxProps
59
+ isOpen: controlledIsOpen,
60
+ defaultOpen = true,
61
+ onOpenChange
73
62
  }) => {
74
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_seeds_react_container.Container, { as: "section", ...boxProps, children: [
75
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ContentBlockHeader, { children: [
76
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_seeds_react_box2.Box, { children: [
77
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_text.Text.Headline, { as: titleAs, children: title }),
78
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_text.Text.Byline, { as: subtitleAs, fontWeight: "normal", children: subtitle })
79
- ] }),
80
- headerActions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_box2.Box, { children: headerActions })
81
- ] }),
63
+ const [internalOpen, setInternalOpen] = (0, import_react.useState)(defaultOpen);
64
+ const open = controlledIsOpen !== void 0 ? controlledIsOpen : internalOpen;
65
+ const handleOpenChange = (newOpen) => {
66
+ if (controlledIsOpen === void 0) {
67
+ setInternalOpen(newOpen);
68
+ }
69
+ onOpenChange?.(newOpen);
70
+ };
71
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_collapsible.Collapsible, { isOpen: open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_seeds_react_container.Container, { as: "section", children: [
72
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
73
+ import_seeds_react_content_header.CollapsibleContentHeader,
74
+ {
75
+ title,
76
+ subtitle,
77
+ headingLevel: titleAs,
78
+ subtitleAs,
79
+ headerActions,
80
+ trigger: import_seeds_react_collapsible.Collapsible.Trigger,
81
+ triggerPosition: "left"
82
+ }
83
+ ),
84
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_collapsible.Collapsible.Panel, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContentArea, { isLoading, contentProps, children }) })
85
+ ] }) });
86
+ };
87
+ var ContentBlock = ({ isCollapsible, ...props }) => {
88
+ if (isCollapsible) {
89
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapsibleContentBlock, { ...props });
90
+ }
91
+ const {
92
+ title,
93
+ subtitle,
94
+ titleAs = "h2",
95
+ subtitleAs = "h3",
96
+ children,
97
+ isLoading,
98
+ headerActions,
99
+ contentProps
100
+ } = props;
101
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_seeds_react_container.Container, { as: "section", children: [
82
102
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
83
- import_seeds_react_box2.Box,
103
+ import_seeds_react_content_header.ContentHeader,
84
104
  {
85
- p: 400,
86
- ...contentProps,
87
- borderBottomLeftRadius: "outer",
88
- borderBottomRightRadius: "outer",
89
- 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
105
+ title,
106
+ subtitle,
107
+ headingLevel: titleAs,
108
+ subtitleAs,
109
+ headerActions
90
110
  }
91
- )
111
+ ),
112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ContentArea, { isLoading, contentProps, children })
92
113
  ] });
93
114
  };
94
115
  var ContentBlock_default = ContentBlock;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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 from \"react\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\nimport { Text } from \"@sproutsocial/seeds-react-text\";\nimport { Loader } from \"@sproutsocial/seeds-react-loader\";\nimport { ContentBlockHeader } from \"./styles\";\nimport { Container } from \"@sproutsocial/seeds-react-container\";\n\nexport interface ContentBlockProps {\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\nconst ContentBlock: React.FC<ContentBlockProps> = ({\n title,\n subtitle,\n titleAs = \"h2\",\n subtitleAs = \"h3\",\n children,\n isLoading,\n headerActions,\n contentProps,\n ...boxProps\n}) => {\n return (\n <Container as=\"section\" {...boxProps}>\n <ContentBlockHeader>\n <Box>\n <Text.Headline as={titleAs}>{title}</Text.Headline>\n <Text.Byline as={subtitleAs} fontWeight=\"normal\">\n {subtitle}\n </Text.Byline>\n </Box>\n {headerActions && <Box>{headerActions}</Box>}\n </ContentBlockHeader>\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 </Container>\n );\n};\n\nexport default ContentBlock;\n","import styled from \"styled-components\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\n\nexport const ContentBlockHeader = styled(Box)`\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: ${({ theme }) => theme.borderWidths[500]} solid\n ${({ theme }) => theme.colors.container.border.base};\n color: ${({ theme }) => theme.colors.text.headline};\n padding: ${({ theme }) => theme.space[400]};\n`;\n\nexport const ContentBlockContent = styled(Box)`\n padding: ${({ theme }) => theme.space[400]};\n`;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAkB;AAClB,IAAAA,0BAAoB;AACpB,8BAAqB;AACrB,gCAAuB;;;ACHvB,+BAAmB;AACnB,6BAAoB;AAEb,IAAM,yBAAqB,yBAAAC,SAAO,0BAAG;AAAA;AAAA;AAAA;AAAA,mBAIzB,CAAC,EAAE,MAAM,MAAM,MAAM,aAAa,GAAG,CAAC;AAAA,MACnD,CAAC,EAAE,MAAM,MAAM,MAAM,OAAO,UAAU,OAAO,IAAI;AAAA,WAC5C,CAAC,EAAE,MAAM,MAAM,MAAM,OAAO,KAAK,QAAQ;AAAA,aACvC,CAAC,EAAE,MAAM,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA;AAGrC,IAAM,0BAAsB,yBAAAA,SAAO,0BAAG;AAAA,aAChC,CAAC,EAAE,MAAM,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA;;;ADT5C,mCAA0B;AA2BlB;AAdR,IAAM,eAA4C,CAAC;AAAA,EACjD;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAM;AACJ,SACE,6CAAC,0CAAU,IAAG,WAAW,GAAG,UAC1B;AAAA,iDAAC,sBACC;AAAA,mDAAC,+BACC;AAAA,oDAAC,6BAAK,UAAL,EAAc,IAAI,SAAU,iBAAM;AAAA,QACnC,4CAAC,6BAAK,QAAL,EAAY,IAAI,YAAY,YAAW,UACrC,oBACH;AAAA,SACF;AAAA,MACC,iBAAiB,4CAAC,+BAAK,yBAAc;AAAA,OACxC;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,GAAG;AAAA,QACF,GAAG;AAAA,QACJ,wBAAuB;AAAA,QACvB,yBAAwB;AAAA,QAEvB,sBACC,4CAAC,+BAAI,IAAI,KACP,sDAAC,oCAAO,OAAO,OAAO,GACxB,IAEA;AAAA;AAAA,IAEJ;AAAA,KACF;AAEJ;AAEA,IAAO,uBAAQ;;;ADtDf,IAAO,gBAAQ;","names":["import_seeds_react_box","styled"]}
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-content-block",
3
- "version": "0.2.13",
3
+ "version": "0.3.3",
4
4
  "description": "Seeds React ContentBlock",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -18,14 +18,13 @@
18
18
  "test:watch": "jest --watch --coverage=false"
19
19
  },
20
20
  "dependencies": {
21
- "@sproutsocial/seeds-react-box": "^1.1.13",
22
- "@sproutsocial/seeds-react-button": "^1.3.18",
23
- "@sproutsocial/seeds-react-container": "^0.3.6",
24
- "@sproutsocial/seeds-react-icon": "^2.2.4",
25
- "@sproutsocial/seeds-react-loader": "^1.0.12",
26
- "@sproutsocial/seeds-react-system-props": "^3.0.2",
27
- "@sproutsocial/seeds-react-text": "^1.4.0",
28
- "@sproutsocial/seeds-react-theme": "^3.5.1"
21
+ "@sproutsocial/seeds-react-box": "^1.1.14",
22
+ "@sproutsocial/seeds-react-button": "^1.4.1",
23
+ "@sproutsocial/seeds-react-collapsible": "^2.0.0",
24
+ "@sproutsocial/seeds-react-container": "^0.3.8",
25
+ "@sproutsocial/seeds-react-content-header": "^0.2.2",
26
+ "@sproutsocial/seeds-react-loader": "^1.0.14",
27
+ "@sproutsocial/seeds-react-theme": "^3.6.0"
29
28
  },
30
29
  "devDependencies": {
31
30
  "@types/react": "^18.0.0",
@@ -1,3 +1,4 @@
1
+ import React, { useState } from "react";
1
2
  import ContentBlock from "./ContentBlock";
2
3
  import type { Meta, StoryObj } from "@storybook/react";
3
4
  import { Text } from "@sproutsocial/seeds-react-text";
@@ -50,3 +51,70 @@ export const WithContentStyles: Story = {
50
51
  <ContentBlock {...args} contentProps={{ p: 0, py: 400, bg: "blue.300" }} />
51
52
  ),
52
53
  };
54
+
55
+ export const Collapsible: Story = {
56
+ render: (args) => (
57
+ <ContentBlock {...args} isCollapsible>
58
+ <Text.SmallBodyCopy>
59
+ This content can be collapsed and expanded by clicking the header.
60
+ </Text.SmallBodyCopy>
61
+ </ContentBlock>
62
+ ),
63
+ };
64
+
65
+ export const CollapsibleDefaultClosed: Story = {
66
+ name: "Collapsible (Default Closed)",
67
+ render: (args) => (
68
+ <ContentBlock {...args} isCollapsible defaultOpen={false}>
69
+ <Text.SmallBodyCopy>
70
+ This content starts collapsed and can be expanded by clicking the
71
+ header.
72
+ </Text.SmallBodyCopy>
73
+ </ContentBlock>
74
+ ),
75
+ };
76
+
77
+ export const CollapsibleWithHeaderActions: Story = {
78
+ name: "Collapsible with Header Actions",
79
+ render: (args) => (
80
+ <ContentBlock
81
+ {...args}
82
+ isCollapsible
83
+ headerActions={
84
+ <Button size="small">
85
+ <Icon name="face-frown-open-solid" />
86
+ </Button>
87
+ }
88
+ >
89
+ <Text.SmallBodyCopy>
90
+ The header actions are independent from the collapse trigger. Clicking
91
+ the action button does not toggle the content.
92
+ </Text.SmallBodyCopy>
93
+ </ContentBlock>
94
+ ),
95
+ };
96
+
97
+ export const CollapsibleControlled: Story = {
98
+ name: "Collapsible (Controlled)",
99
+ render: (args) => {
100
+ const [isOpen, setIsOpen] = useState(true);
101
+ return (
102
+ <>
103
+ <Button onClick={() => setIsOpen(!isOpen)} mb={400}>
104
+ {isOpen ? "Close" : "Open"} externally
105
+ </Button>
106
+ <ContentBlock
107
+ {...args}
108
+ isCollapsible
109
+ isOpen={isOpen}
110
+ onOpenChange={setIsOpen}
111
+ >
112
+ <Text.SmallBodyCopy>
113
+ This content block is controlled externally. Use the button above or
114
+ click the header to toggle.
115
+ </Text.SmallBodyCopy>
116
+ </ContentBlock>
117
+ </>
118
+ );
119
+ },
120
+ };
@@ -1,22 +1,68 @@
1
- import React from "react";
1
+ import React, { useState } from "react";
2
2
  import { Box } from "@sproutsocial/seeds-react-box";
3
- import { Text } from "@sproutsocial/seeds-react-text";
4
3
  import { Loader } from "@sproutsocial/seeds-react-loader";
5
- import { ContentBlockHeader } from "./styles";
4
+ import { Collapsible } from "@sproutsocial/seeds-react-collapsible";
5
+ import {
6
+ ContentHeader,
7
+ CollapsibleContentHeader,
8
+ } from "@sproutsocial/seeds-react-content-header";
6
9
  import { Container } from "@sproutsocial/seeds-react-container";
7
10
 
8
- export interface ContentBlockProps {
11
+ interface ContentBlockBaseProps {
9
12
  title: string;
10
13
  subtitle: string;
11
- titleAs: "h1" | "h2" | "h3" | "h4";
12
- subtitleAs: "h2" | "h3" | "h4" | "h5" | "p";
14
+ titleAs?: "h1" | "h2" | "h3" | "h4";
15
+ subtitleAs?: "h2" | "h3" | "h4" | "h5" | "p";
13
16
  isLoading?: boolean;
14
17
  children: React.ReactNode;
15
18
  headerActions?: React.ReactNode;
16
19
  contentProps?: React.ComponentProps<typeof Box>;
17
20
  }
18
21
 
19
- const ContentBlock: React.FC<ContentBlockProps> = ({
22
+ type StaticContentBlockProps = ContentBlockBaseProps & {
23
+ isCollapsible?: false;
24
+ isOpen?: never;
25
+ defaultOpen?: never;
26
+ onOpenChange?: never;
27
+ };
28
+
29
+ type CollapsibleContentBlockProps = ContentBlockBaseProps & {
30
+ /** Enables collapse/expand behavior on the content area */
31
+ isCollapsible: true;
32
+ /** Controlled open state. When provided, the component is in controlled mode. */
33
+ isOpen?: boolean;
34
+ /** Initial open state for uncontrolled mode. Defaults to true. */
35
+ defaultOpen?: boolean;
36
+ /** Called when the open state changes */
37
+ onOpenChange?: (open: boolean) => void;
38
+ };
39
+
40
+ export type ContentBlockProps =
41
+ | StaticContentBlockProps
42
+ | CollapsibleContentBlockProps;
43
+
44
+ const ContentArea = ({
45
+ isLoading,
46
+ children,
47
+ contentProps,
48
+ }: Pick<ContentBlockBaseProps, "isLoading" | "children" | "contentProps">) => (
49
+ <Box
50
+ p={400}
51
+ {...contentProps}
52
+ borderBottomLeftRadius="outer"
53
+ borderBottomRightRadius="outer"
54
+ >
55
+ {isLoading ? (
56
+ <Box my={400}>
57
+ <Loader delay={false} />
58
+ </Box>
59
+ ) : (
60
+ children
61
+ )}
62
+ </Box>
63
+ );
64
+
65
+ const CollapsibleContentBlock = ({
20
66
  title,
21
67
  subtitle,
22
68
  titleAs = "h2",
@@ -25,33 +71,70 @@ const ContentBlock: React.FC<ContentBlockProps> = ({
25
71
  isLoading,
26
72
  headerActions,
27
73
  contentProps,
28
- ...boxProps
29
- }) => {
74
+ isOpen: controlledIsOpen,
75
+ defaultOpen = true,
76
+ onOpenChange,
77
+ }: Omit<CollapsibleContentBlockProps, "isCollapsible">) => {
78
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
79
+ const open = controlledIsOpen !== undefined ? controlledIsOpen : internalOpen;
80
+
81
+ const handleOpenChange = (newOpen: boolean) => {
82
+ if (controlledIsOpen === undefined) {
83
+ setInternalOpen(newOpen);
84
+ }
85
+ onOpenChange?.(newOpen);
86
+ };
87
+
30
88
  return (
31
- <Container as="section" {...boxProps}>
32
- <ContentBlockHeader>
33
- <Box>
34
- <Text.Headline as={titleAs}>{title}</Text.Headline>
35
- <Text.Byline as={subtitleAs} fontWeight="normal">
36
- {subtitle}
37
- </Text.Byline>
38
- </Box>
39
- {headerActions && <Box>{headerActions}</Box>}
40
- </ContentBlockHeader>
41
- <Box
42
- p={400}
43
- {...contentProps}
44
- borderBottomLeftRadius="outer"
45
- borderBottomRightRadius="outer"
46
- >
47
- {isLoading ? (
48
- <Box my={400}>
49
- <Loader delay={false} />
50
- </Box>
51
- ) : (
52
- children
53
- )}
54
- </Box>
89
+ <Collapsible isOpen={open} onOpenChange={handleOpenChange}>
90
+ <Container as="section">
91
+ <CollapsibleContentHeader
92
+ title={title}
93
+ subtitle={subtitle}
94
+ headingLevel={titleAs}
95
+ subtitleAs={subtitleAs}
96
+ headerActions={headerActions}
97
+ trigger={Collapsible.Trigger}
98
+ triggerPosition="left"
99
+ />
100
+ <Collapsible.Panel>
101
+ <ContentArea isLoading={isLoading} contentProps={contentProps}>
102
+ {children}
103
+ </ContentArea>
104
+ </Collapsible.Panel>
105
+ </Container>
106
+ </Collapsible>
107
+ );
108
+ };
109
+
110
+ const ContentBlock = ({ isCollapsible, ...props }: ContentBlockProps) => {
111
+ if (isCollapsible) {
112
+ return <CollapsibleContentBlock {...props} />;
113
+ }
114
+
115
+ const {
116
+ title,
117
+ subtitle,
118
+ titleAs = "h2",
119
+ subtitleAs = "h3",
120
+ children,
121
+ isLoading,
122
+ headerActions,
123
+ contentProps,
124
+ } = props;
125
+
126
+ return (
127
+ <Container as="section">
128
+ <ContentHeader
129
+ title={title}
130
+ subtitle={subtitle}
131
+ headingLevel={titleAs}
132
+ subtitleAs={subtitleAs}
133
+ headerActions={headerActions}
134
+ />
135
+ <ContentArea isLoading={isLoading} contentProps={contentProps}>
136
+ {children}
137
+ </ContentArea>
55
138
  </Container>
56
139
  );
57
140
  };
@@ -1,4 +1,9 @@
1
- import { render, screen } from "@sproutsocial/seeds-react-testing-library";
1
+ import React, { useState } from "react";
2
+ import {
3
+ render,
4
+ screen,
5
+ fireEvent,
6
+ } from "@sproutsocial/seeds-react-testing-library";
2
7
  import ContentBlock from "../ContentBlock";
3
8
 
4
9
  describe("ContentBlock Features", () => {
@@ -56,3 +61,150 @@ describe("ContentBlock Features", () => {
56
61
  ).toBeInTheDocument();
57
62
  });
58
63
  });
64
+
65
+ describe("ContentBlock Collapsible", () => {
66
+ test("does not render chevron when not collapsible", () => {
67
+ render(
68
+ <ContentBlock title="Title" subtitle="Subtitle">
69
+ Content
70
+ </ContentBlock>
71
+ );
72
+
73
+ expect(screen.queryByRole("button")).not.toBeInTheDocument();
74
+ });
75
+
76
+ test("renders chevron icon when collapsible", () => {
77
+ render(
78
+ <ContentBlock title="Title" subtitle="Subtitle" isCollapsible>
79
+ Content
80
+ </ContentBlock>
81
+ );
82
+
83
+ const trigger = screen.getByRole("button");
84
+ expect(trigger).toBeInTheDocument();
85
+ });
86
+
87
+ test("defaults to open when collapsible", () => {
88
+ render(
89
+ <ContentBlock title="Title" subtitle="Subtitle" isCollapsible>
90
+ Visible Content
91
+ </ContentBlock>
92
+ );
93
+
94
+ const trigger = screen.getByRole("button");
95
+ expect(trigger).toHaveAttribute("aria-expanded", "true");
96
+ expect(screen.getByText("Visible Content")).toBeInTheDocument();
97
+ });
98
+
99
+ test("can default to closed", () => {
100
+ render(
101
+ <ContentBlock
102
+ title="Title"
103
+ subtitle="Subtitle"
104
+ isCollapsible
105
+ defaultOpen={false}
106
+ >
107
+ Hidden Content
108
+ </ContentBlock>
109
+ );
110
+
111
+ const trigger = screen.getByRole("button");
112
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
113
+ });
114
+
115
+ test("toggles content on trigger click", () => {
116
+ render(
117
+ <ContentBlock title="Title" subtitle="Subtitle" isCollapsible>
118
+ Toggle Content
119
+ </ContentBlock>
120
+ );
121
+
122
+ const trigger = screen.getByRole("button");
123
+ expect(trigger).toHaveAttribute("aria-expanded", "true");
124
+
125
+ fireEvent.click(trigger);
126
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
127
+
128
+ fireEvent.click(trigger);
129
+ expect(trigger).toHaveAttribute("aria-expanded", "true");
130
+ });
131
+
132
+ test("controlled mode respects isOpen prop", () => {
133
+ const ControlledContentBlock = () => {
134
+ const [open, setOpen] = useState(false);
135
+ return (
136
+ <>
137
+ <button onClick={() => setOpen(!open)}>External Toggle</button>
138
+ <ContentBlock
139
+ title="Title"
140
+ subtitle="Subtitle"
141
+ isCollapsible
142
+ isOpen={open}
143
+ onOpenChange={setOpen}
144
+ >
145
+ Controlled Content
146
+ </ContentBlock>
147
+ </>
148
+ );
149
+ };
150
+
151
+ render(<ControlledContentBlock />);
152
+
153
+ const trigger = screen.getByRole("button", { name: /External Toggle/i });
154
+ const collapsibleTrigger = screen
155
+ .getAllByRole("button")
156
+ .find((btn) => btn.getAttribute("aria-expanded") !== null);
157
+
158
+ expect(collapsibleTrigger).toHaveAttribute("aria-expanded", "false");
159
+
160
+ fireEvent.click(trigger);
161
+ expect(collapsibleTrigger).toHaveAttribute("aria-expanded", "true");
162
+ });
163
+
164
+ test("headerActions render alongside trigger and do not toggle collapse", () => {
165
+ const actionClick = jest.fn();
166
+
167
+ render(
168
+ <ContentBlock
169
+ title="Title"
170
+ subtitle="Subtitle"
171
+ isCollapsible
172
+ headerActions={<button onClick={actionClick}>Action</button>}
173
+ >
174
+ Content
175
+ </ContentBlock>
176
+ );
177
+
178
+ const actionButton = screen.getByText("Action");
179
+ expect(actionButton).toBeInTheDocument();
180
+
181
+ const collapsibleTrigger = screen
182
+ .getAllByRole("button")
183
+ .find((btn) => btn.getAttribute("aria-expanded") !== null);
184
+ expect(collapsibleTrigger).toHaveAttribute("aria-expanded", "true");
185
+
186
+ fireEvent.click(actionButton);
187
+ expect(actionClick).toHaveBeenCalledTimes(1);
188
+ // Collapse state should not change from clicking the action
189
+ expect(collapsibleTrigger).toHaveAttribute("aria-expanded", "true");
190
+ });
191
+
192
+ test("calls onOpenChange when toggled", () => {
193
+ const onOpenChange = jest.fn();
194
+
195
+ render(
196
+ <ContentBlock
197
+ title="Title"
198
+ subtitle="Subtitle"
199
+ isCollapsible
200
+ onOpenChange={onOpenChange}
201
+ >
202
+ Content
203
+ </ContentBlock>
204
+ );
205
+
206
+ const trigger = screen.getByRole("button");
207
+ fireEvent.click(trigger);
208
+ expect(onOpenChange).toHaveBeenCalledWith(false);
209
+ });
210
+ });
package/src/styles.ts CHANGED
@@ -1,16 +1,6 @@
1
1
  import styled from "styled-components";
2
2
  import { Box } from "@sproutsocial/seeds-react-box";
3
3
 
4
- export const ContentBlockHeader = styled(Box)`
5
- display: flex;
6
- justify-content: space-between;
7
- align-items: center;
8
- border-bottom: ${({ theme }) => theme.borderWidths[500]} solid
9
- ${({ theme }) => theme.colors.container.border.base};
10
- color: ${({ theme }) => theme.colors.text.headline};
11
- padding: ${({ theme }) => theme.space[400]};
12
- `;
13
-
14
4
  export const ContentBlockContent = styled(Box)`
15
5
  padding: ${({ theme }) => theme.space[400]};
16
6
  `;