Browse Source

docs: Create a stories files for Collapsible, CollapsePanel and ClippedBox (#56875)

<img width="1327" alt="SCR-20231003-pjms"
src="https://github.com/getsentry/sentry/assets/187460/5bb9c132-d6e3-4b9d-b584-7a6a2d95a2a2">
Ryan Albrecht 1 year ago
parent
commit
cae2b61135

+ 117 - 0
static/app/components/clippedBox.stories.tsx

@@ -0,0 +1,117 @@
+import {Fragment, useState} from 'react';
+import styled from '@emotion/styled';
+
+import onboardingFrameworkSelectionJavascript from 'sentry-images/spot/onboarding-framework-selection-javascript.svg';
+
+import ClippedBox from 'sentry/components/clippedBox';
+import JSXNode from 'sentry/components/stories/jsxNode';
+import JSXProperty from 'sentry/components/stories/jsxProperty';
+import Matrix from 'sentry/components/stories/matrix';
+import SideBySide from 'sentry/components/stories/sideBySide';
+import SizingWindow from 'sentry/components/stories/sizingWindow';
+import storyBook from 'sentry/stories/storyBook';
+import {space} from 'sentry/styles/space';
+
+export default storyBook(ClippedBox, story => {
+  story('Default', () => (
+    <Fragment>
+      <p>
+        By default <JSXNode name="ClippedBox" /> is just a container. Add{' '}
+        <JSXProperty name="defaultClipped" value /> to occlude the bottom part of the
+        content if the content height is larger than{' '}
+        <JSXProperty name="clipHeight" value={Number} />. You should also set{' '}
+        <JSXProperty name="clipHeight" value={Number} /> (default: 300) to be something
+        reasonable for the situation.
+      </p>
+      <p>
+        Once expanded, <JSXNode name="ClippedBox" /> cannot be collapsed again.
+      </p>
+      <SizingWindow>
+        <ClippedBox>
+          <img src={onboardingFrameworkSelectionJavascript} height={300} />
+        </ClippedBox>
+      </SizingWindow>
+    </Fragment>
+  ));
+
+  const CustomFade = styled('div')`
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    padding: ${space(1)};
+    background: ${p => p.theme.background};
+    text-align: center;
+    pointer-events: none;
+  `;
+
+  story('Title', () => (
+    <SizingWindow>
+      <ClippedBox title="This is the title">
+        <img src={onboardingFrameworkSelectionJavascript} height={300} />
+      </ClippedBox>
+    </SizingWindow>
+  ));
+
+  story('Custom Button & Fade', () => (
+    <Matrix
+      render={ClippedBox}
+      propMatrix={{
+        btnText: ['Custom Label'],
+        buttonProps: [undefined, {priority: 'danger'}],
+        clipHeight: [100],
+        clipFade: [
+          undefined,
+          ({showMoreButton}) => <CustomFade>{showMoreButton}</CustomFade>,
+        ],
+        children: [
+          <img key="img" src={onboardingFrameworkSelectionJavascript} height={150} />,
+        ],
+      }}
+      selectedProps={['buttonProps', 'clipFade']}
+    />
+  ));
+
+  story('Callbacks', () => {
+    return (
+      <Fragment>
+        <p>
+          Some callbacks are available:{' '}
+          <JSXProperty name="onSetRenderedHeight" value={Function} />
+          & <JSXProperty name="onReveal" value={Function} />.
+        </p>
+        <SideBySide>
+          {[50, 100, 150].map(imgHeight => {
+            const [isRevealed, setIsRevealed] = useState(false);
+            const [renderedHeight, setRenderedHeight] = useState<number | undefined>(
+              undefined
+            );
+            return (
+              <div key={imgHeight}>
+                <p>
+                  <JSXNode name="ClippedBox" props={{clipHeight: 100}}>
+                    <JSXNode name="img" props={{height: imgHeight}} />
+                  </JSXNode>
+                </p>
+                <p>isRevealed = {String(isRevealed)}</p>
+                <p>renderedHeight = {renderedHeight}</p>
+                <SizingWindow>
+                  <ClippedBox
+                    clipHeight={100}
+                    onReveal={() => setIsRevealed(true)}
+                    onSetRenderedHeight={setRenderedHeight}
+                  >
+                    <img
+                      src={onboardingFrameworkSelectionJavascript}
+                      height={imgHeight}
+                    />
+                  </ClippedBox>
+                </SizingWindow>
+              </div>
+            );
+          })}
+        </SideBySide>
+      </Fragment>
+    );
+  });
+});

+ 112 - 0
static/app/components/collapsePanel.stories.tsx

@@ -0,0 +1,112 @@
+import {Fragment} from 'react';
+
+import CollapsePanel from 'sentry/components/collapsePanel';
+import JSXNode from 'sentry/components/stories/jsxNode';
+import JSXProperty from 'sentry/components/stories/jsxProperty';
+import Matrix from 'sentry/components/stories/matrix';
+import SideBySide from 'sentry/components/stories/sideBySide';
+import SizingWindow from 'sentry/components/stories/sizingWindow';
+import storyBook from 'sentry/stories/storyBook';
+
+export default storyBook(CollapsePanel, story => {
+  story('Basics', () => (
+    <Fragment>
+      <p>
+        The <JSXNode name="CollapsePanel" /> expanded state will be set based on whether{' '}
+        <JSXProperty name="items" value={Number} /> is larger than{' '}
+        <JSXProperty name="collapseCount" value={Number} /> (default: 5).
+      </p>
+      <p>
+        Once expanded, <JSXNode name="CollapsePanel" /> cannot be collapsed again.
+      </p>
+      <SizingWindow display="block">
+        <CollapsePanel items={6}>
+          {({isExpanded, showMoreButton}) => (
+            <Fragment>
+              <p>isExpanded = {String(isExpanded)}</p>
+              {showMoreButton}
+            </Fragment>
+          )}
+        </CollapsePanel>
+      </SizingWindow>
+    </Fragment>
+  ));
+
+  story('Bugs', () => (
+    <Fragment>
+      <p>
+        Starting with items less than or equal to the <var>collapseCount</var> will return
+        an incorrect <var>isExpanded</var> value.
+      </p>
+      <SideBySide>
+        {[6, 5, 4].map(items => (
+          <Fragment key={items}>
+            <JSXNode name="CollapsePanel" props={{items, collapseCount: 5}} />
+            <SizingWindow display="block">
+              <CollapsePanel items={items} collapseCount={5}>
+                {({isExpanded, showMoreButton}) => (
+                  <Fragment>
+                    <p>isExpanded = {String(isExpanded)}</p>
+                    {showMoreButton}
+                  </Fragment>
+                )}
+              </CollapsePanel>
+            </SizingWindow>
+          </Fragment>
+        ))}
+      </SideBySide>
+    </Fragment>
+  ));
+
+  story('Rendering a list', () => {
+    const allItems = ['one', 'two', 'three', 'four', 'five'];
+    const collapseCount = 3; // Show 3 items to start
+    return (
+      <Fragment>
+        <p>
+          Typically you will render a portion of the list when the panel is collapsed,
+          then all items when the panel is expanded.
+        </p>
+        <SizingWindow display="block">
+          <CollapsePanel items={allItems.length} collapseCount={collapseCount}>
+            {({isExpanded, showMoreButton}) => {
+              const items = isExpanded ? allItems : allItems.slice(0, collapseCount);
+              return (
+                <Fragment>
+                  <ul>
+                    {items.map(item => (
+                      <li key={item}>{item}</li>
+                    ))}
+                  </ul>
+                  {showMoreButton}
+                </Fragment>
+              );
+            }}
+          </CollapsePanel>
+        </SizingWindow>
+      </Fragment>
+    );
+  });
+
+  story('Props', () => (
+    <Matrix
+      render={CollapsePanel}
+      propMatrix={{
+        buttonTitle: [undefined, 'Custom Title'],
+        collapseCount: [0],
+        disableBorder: [true, false],
+        items: [1],
+        children: [
+          ({isExpanded, showMoreButton}) => (
+            <Fragment>
+              <p>isExpanded = {String(isExpanded)}</p>
+              {showMoreButton}
+            </Fragment>
+          ),
+        ],
+      }}
+      selectedProps={['buttonTitle', 'disableBorder']}
+      sizingWindowProps={{display: 'block'}}
+    />
+  ));
+});

+ 1 - 0
static/app/components/collapsePanel.tsx

@@ -22,6 +22,7 @@ type Props = {
 };
 
 /**
+ *
  * Used to expand results.
  *
  * Our collapsible component was not used because we want our

+ 109 - 0
static/app/components/collapsible.stories.tsx

@@ -0,0 +1,109 @@
+import {Fragment} from 'react';
+
+import {Button} from 'sentry/components/button';
+import Collapsible from 'sentry/components/collapsible';
+import JSXNode from 'sentry/components/stories/jsxNode';
+import JSXProperty from 'sentry/components/stories/jsxProperty';
+import SideBySide from 'sentry/components/stories/sideBySide';
+import SizingWindow from 'sentry/components/stories/sizingWindow';
+import storyBook from 'sentry/stories/storyBook';
+
+export default storyBook(Collapsible, story => {
+  story('Default', () => (
+    <Fragment>
+      <p>
+        After passing in a list of children, <JSXNode name="Collapsible" /> will truncate
+        the list to be a max of <JSXProperty name="maxVisibleItems" value={Number} />{' '}
+        long.
+      </p>
+      <SizingWindow display="block">
+        <Collapsible maxVisibleItems={3}>
+          <div>Item 1</div>
+          <div>Item 2</div>
+          <div>Item 3</div>
+          <div>Item 4</div>
+          <div>Item 5</div>
+        </Collapsible>
+      </SizingWindow>
+    </Fragment>
+  ));
+
+  story('Bugs', () => (
+    <Fragment>
+      <p>
+        It's possible to use <JSXNode name="ul" /> or <JSXNode name="ol" />, but beware
+        that the button will appear inside the list as well.
+      </p>
+      <SideBySide>
+        <ol>
+          <Collapsible maxVisibleItems={2}>
+            <li>Item 1</li>
+            <li>Item 2</li>
+            <li>Item 3</li>
+          </Collapsible>
+        </ol>
+        <ul>
+          <Collapsible maxVisibleItems={2}>
+            <li>Item 1</li>
+            <li>Item 2</li>
+            <li>Item 3</li>
+          </Collapsible>
+        </ul>
+      </SideBySide>
+    </Fragment>
+  ));
+
+  story('maxVisibleItems', () => {
+    const allItems = ['one', 'two', 'three', 'four', 'five'].map(i => (
+      <div key={i}>Item {i}</div>
+    ));
+
+    return (
+      <Fragment>
+        <p>
+          <JSXProperty name="maxVisibleItems" value={Number} /> will show/hide and
+          pluralize the button label as needed.
+        </p>
+        <SideBySide>
+          {[3, 4, 5, 6].map(maxVisibleItems => (
+            <div key={maxVisibleItems}>
+              <p>
+                <JSXProperty name="maxVisibleItems" value={maxVisibleItems} />
+              </p>
+              <SizingWindow display="block">
+                <Collapsible maxVisibleItems={maxVisibleItems}>{allItems}</Collapsible>
+              </SizingWindow>
+            </div>
+          ))}
+        </SideBySide>
+      </Fragment>
+    );
+  });
+
+  story('Custom Buttons', () => (
+    <Fragment>
+      <p>
+        You can set custom <JSXProperty name="collapseButton" value={Function} /> &{' '}
+        <JSXProperty name="expandeButton" value={Function} />, and they will always be
+        rendered in the same spot, at the bottom of the list.
+      </p>
+      <Collapsible
+        maxVisibleItems={2}
+        collapseButton={({onCollapse}) => (
+          <Button size="xs" onClick={onCollapse}>
+            Collapse
+          </Button>
+        )}
+        expandButton={({numberOfHiddenItems, onExpand}) => (
+          <Button size="xs" onClick={onExpand}>
+            Expand ({numberOfHiddenItems} hidden)
+          </Button>
+        )}
+      >
+        <li>Item 1</li>
+        <li>Item 2</li>
+        <li>Item 3</li>
+      </Collapsible>
+    </Fragment>
+  ));
+});

+ 10 - 5
static/app/components/stories/matrix.tsx

@@ -81,11 +81,16 @@ export default function Matrix<P extends RenderProps>({
 function item(Component, props, sizingWindowProps) {
   const hasChildren = 'children' in props;
 
-  return hasChildren ? (
-    <SizingWindow key={JSON.stringify(props)} {...sizingWindowProps}>
-      <Component {...props}>{props.children}</Component>
-    </SizingWindow>
-  ) : (
+  if (hasChildren) {
+    const {children, ...otherProps} = props;
+    return (
+      <SizingWindow key={JSON.stringify(otherProps)} {...sizingWindowProps}>
+        <Component {...otherProps}>{children}</Component>
+      </SizingWindow>
+    );
+  }
+
+  return (
     <SizingWindow key={JSON.stringify(props)} {...sizingWindowProps}>
       <Component {...props} />
     </SizingWindow>