Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/red-years-bet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/form-footer': patch
---

Added internal container to preserve component styles when sticky positioning is applied externally
39 changes: 38 additions & 1 deletion packages/form-footer/src/FormFooter.stories.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing changeset

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { css } from '@leafygreen-ui/emotion';
import { Icon } from '@leafygreen-ui/icon';
import { MenuItem } from '@leafygreen-ui/menu';

import FormFooter, { FormFooterProps } from '.';
import { FormFooter, FormFooterProps } from '.';

const wrapperStyle = css`
min-width: 40vw;
Expand Down Expand Up @@ -80,6 +80,31 @@ const Template: StoryType<typeof FormFooter> = ({
/>
);

const StickyTemplate: StoryType<typeof FormFooter> = ({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is intended to be a chromatic snapshot, it could be made more realistic by setting overflow: hidden; on the scrolling div and making this a play test that scrolls to a fixed point before taking the snapshot. There are a couple examples of this in the Drawer stories if you search for a ScrollableAndPadded story

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we necessarily need to test that the sticky behavior works on scroll, and just leave it with a test confirming that adding position: sticky; doesn't break it

primaryButtonProps,
...args
}: FormFooterStoryProps) => (
<div style={{ margin: `-100px` }}>
<div
className={css`
padding: 24px;
height: 100vh;
overflow: hidden;
`}
>
<h2>Scroll down to see the sticky footer in action</h2>
<p>This content creates scrollable space...</p>
</div>
<FormFooter
{...args}
primaryButtonProps={{ children: 'Button', variant: 'primary' }}
className={css`
position: sticky;
bottom: 0;
`}
/>
</div>
);
export const LiveExample: StoryObj<FormFooterProps> = {
render: Template,
parameters: {
Expand Down Expand Up @@ -120,3 +145,15 @@ export const DarkMode: StoryObj<typeof FormFooter> = {
darkMode: true,
},
};

export const StickyFooter: StoryObj<typeof FormFooter> = {
render: StickyTemplate,
decorators: [],
parameters: {
layout: 'fullscreen',
},
args: {
backButtonProps: { children: 'Back' },
cancelButtonProps: { children: 'Cancel' },
},
};
40 changes: 25 additions & 15 deletions packages/form-footer/src/FormFooter.styles.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { css, cx } from '@leafygreen-ui/emotion';
import { Theme } from '@leafygreen-ui/lib';
import { palette } from '@leafygreen-ui/palette';
import { addOverflowShadow, Side } from '@leafygreen-ui/tokens';
import { addOverflowShadow, Side, spacing } from '@leafygreen-ui/tokens';

const MIN_HEIGHT = 92;

const footerBaseStyle = css`
min-height: 92px;
width: 100%;
padding: 26px 24px;
display: flex;
align-items: center;

button {
white-space: nowrap;
}
`;

const footerThemeStyle: Record<Theme, string> = {
Expand All @@ -25,10 +19,23 @@ const footerThemeStyle: Record<Theme, string> = {
`,
};

export const contentStyle = css`
const innerContainerBaseStyle = css`
min-height: ${MIN_HEIGHT}px;
width: 100%;
padding: ${spacing[600]}px;
display: flex;
align-items: center;
background: inherit;

button {
white-space: nowrap;
}
`;

const contentStyle = css`
display: flex;
align-items: center;
gap: 8px;
gap: ${spacing[200]}px;
width: 100%;
`;

Expand All @@ -51,10 +58,13 @@ export const getFormFooterStyles = ({
}: {
theme: Theme;
className?: string;
}) =>
}) => cx(footerBaseStyle, footerThemeStyle[theme], className);

export const getInnerContainerStyles = ({ theme }: { theme: Theme }) =>
cx(
footerBaseStyle,
footerThemeStyle[theme],
innerContainerBaseStyle,
addOverflowShadow({ side: Side.Top, theme, isInside: false }),
className,
);

export const getContentStyles = (contentClassName?: string) =>
cx(contentStyle, contentClassName);
96 changes: 50 additions & 46 deletions packages/form-footer/src/FormFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';

import { Banner, Variant as BannerVariant } from '@leafygreen-ui/banner';
import { Button, Variant as ButtonVariant } from '@leafygreen-ui/button';
import { cx } from '@leafygreen-ui/emotion';
import ArrowLeftIcon from '@leafygreen-ui/icon/dist/ArrowLeft';
import LeafyGreenProvider, {
useDarkMode,
Expand All @@ -11,9 +10,10 @@ import { SplitButton } from '@leafygreen-ui/split-button';

import {
bannerStyle,
contentStyle,
flexEndContent,
getContentStyles,
getFormFooterStyles,
getInnerContainerStyles,
} from './FormFooter.styles';
import { FormFooterProps } from './FormFooter.types';
import { getLgIds } from './utils';
Expand Down Expand Up @@ -50,59 +50,63 @@ export default function FormFooter({
className={getFormFooterStyles({ theme, className })}
{...rest}
>
<div className={cx(contentStyle, contentClassName)}>
{showBackButton &&
(isStandardBackButton ? (
<Button
variant={ButtonVariant.Default}
leftGlyph={<ArrowLeftIcon data-testid={lgIds.backButtonIcon} />}
data-testid={lgIds.backButton}
{...backButtonProps}
>
{backButtonProps?.children || 'Back'}
</Button>
) : (
<SplitButton
data-testid={lgIds.backSplitButton}
variant={ButtonVariant.Default}
{...backButtonProps}
/>
))}
<div className={flexEndContent}>
{errorMessage && (
<Banner className={bannerStyle} variant={BannerVariant.Danger}>
{errorMessage}
</Banner>
)}
{showCancelButton &&
(isStandardCancelButton ? (
<div className={getInnerContainerStyles({ theme })}>
<div className={getContentStyles(contentClassName)}>
{showBackButton &&
(isStandardBackButton ? (
<Button
data-testid={lgIds.cancelButton}
{...cancelButtonProps}
variant={ButtonVariant.Default}
leftGlyph={
<ArrowLeftIcon data-testid={lgIds.backButtonIcon} />
}
data-testid={lgIds.backButton}
{...backButtonProps}
>
{cancelButtonProps?.children || 'Cancel'}
{backButtonProps?.children || 'Back'}
</Button>
) : (
<SplitButton
data-testid={lgIds.cancelSplitButton}
{...cancelButtonProps}
data-testid={lgIds.backSplitButton}
variant={ButtonVariant.Default}
{...backButtonProps}
/>
))}
{isStandardPrimaryButton ? (
<Button
data-testid={lgIds.primaryButton}
variant={ButtonVariant.Primary}
{...primaryButtonProps}
/>
) : (
<SplitButton
data-testid={lgIds.primarySplitButton}
variant={ButtonVariant.Primary}
{...primaryButtonProps}
/>
)}
<div className={flexEndContent}>
{errorMessage && (
<Banner className={bannerStyle} variant={BannerVariant.Danger}>
{errorMessage}
</Banner>
)}
{showCancelButton &&
(isStandardCancelButton ? (
<Button
data-testid={lgIds.cancelButton}
{...cancelButtonProps}
variant={ButtonVariant.Default}
>
{cancelButtonProps?.children || 'Cancel'}
</Button>
) : (
<SplitButton
data-testid={lgIds.cancelSplitButton}
{...cancelButtonProps}
variant={ButtonVariant.Default}
/>
))}
{isStandardPrimaryButton ? (
<Button
data-testid={lgIds.primaryButton}
variant={ButtonVariant.Primary}
{...primaryButtonProps}
/>
) : (
<SplitButton
data-testid={lgIds.primarySplitButton}
variant={ButtonVariant.Primary}
{...primaryButtonProps}
/>
)}
</div>
</div>
</div>
</footer>
Expand Down
Loading