Skip to content

Commit d14c963

Browse files
authored
feat(Modal)!: introduce 2.0 component (#1907)
- add in style changes for both Modal and dependent `ButtonGroup` - simplify interface and update with new semantics - update test snapshots for chromatic
1 parent 8bce819 commit d14c963

8 files changed

+1257
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*------------------------------------*\
2+
# BUTTON GROUP
3+
\*------------------------------------*/
4+
5+
/**
6+
* A group of buttons displayed in an organized fashion.
7+
*/
8+
.button-group {
9+
display: flex;
10+
flex-wrap: wrap;
11+
justify-content: flex-start;
12+
13+
gap: 0.5rem;
14+
}
15+
16+
.button-group--horizontal {
17+
flex-direction: row-reverse;
18+
}
19+
20+
.button-group--vertical {
21+
flex-direction: column;
22+
align-content: center;
23+
}
24+
25+
.button-group--horizontal-progressive {
26+
flex-direction: row-reverse;
27+
justify-content: space-between;
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { StoryObj, Meta } from '@storybook/react';
2+
import React from 'react';
3+
4+
import { ButtonGroup } from './ButtonGroup-v2';
5+
import { ButtonV2 as Button } from '../Button';
6+
7+
export default {
8+
title: 'Components/V2/ButtonGroup',
9+
component: ButtonGroup,
10+
args: {
11+
orientation: 'horizontal',
12+
children: (
13+
<>
14+
<Button rank="primary">Button 1</Button>
15+
<Button rank="secondary">Button 2</Button>
16+
</>
17+
),
18+
},
19+
argTypes: {
20+
children: {
21+
control: {
22+
type: null,
23+
},
24+
},
25+
},
26+
parameters: {
27+
badges: ['intro-1.0', 'current-2.0'],
28+
},
29+
decorators: [(Story) => <div className="p-8">{Story()}</div>],
30+
} as Meta<Args>;
31+
32+
type Args = React.ComponentProps<typeof ButtonGroup>;
33+
34+
export const Default: StoryObj<Args> = {};
35+
36+
export const Vertical: StoryObj<Args> = {
37+
args: {
38+
buttonLayout: 'vertical',
39+
},
40+
};
41+
42+
export const HorizontalProgressive: StoryObj<Args> = {
43+
args: {
44+
buttonLayout: 'horizontal-progressive',
45+
},
46+
};
47+
48+
export const WithFiveButtons: StoryObj<Args> = {
49+
args: {
50+
children: (
51+
<>
52+
<Button>Button 1</Button>
53+
<Button>Button 2</Button>
54+
<Button>Button 3</Button>
55+
<Button>Button 4</Button>
56+
<Button rank="primary">Button 5</Button>
57+
</>
58+
),
59+
},
60+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import clsx from 'clsx';
2+
import type { ReactNode } from 'react';
3+
import React from 'react';
4+
5+
import styles from './ButtonGroup-v2.module.css';
6+
7+
type ButtonGroupProps = {
8+
// Component API
9+
/**
10+
* The buttons. Should not be wrapped in another element – we just want the buttons.
11+
*/
12+
children: ReactNode;
13+
/**
14+
* Additional classnames passed in for styling.
15+
*
16+
* This will be applied to the container we're placing around the buttons.
17+
*/
18+
className?: string;
19+
// Design API
20+
/**
21+
* Whether the buttons should be laid out horizontally or stacked vertically (along with relative button position).
22+
*/
23+
buttonLayout?: 'horizontal' | 'vertical' | 'horizontal-progressive';
24+
};
25+
26+
/**
27+
* `import {ButtonGroup} from "@chanzuckerberg/eds";`
28+
*
29+
* A container for buttons grouped together horizontally or vertically.
30+
*/
31+
export function ButtonGroup({
32+
children,
33+
className,
34+
buttonLayout = 'horizontal',
35+
}: ButtonGroupProps) {
36+
const componentClassName = clsx(
37+
styles['button-group'],
38+
buttonLayout && styles[`button-group--${buttonLayout}`],
39+
className,
40+
);
41+
42+
return <div className={componentClassName}>{children}</div>;
43+
}

src/components/ButtonGroup/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { ButtonGroup as default } from './ButtonGroup';
2+
export { ButtonGroup as ButtonGroupV2 } from './ButtonGroup-v2';
+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
@import '../../design-tokens/mixins.css';
2+
3+
/*------------------------------------*\
4+
# MODAL
5+
\*------------------------------------*/
6+
7+
/**
8+
* The modal wrapper and overlay which takes up the entire screen.
9+
*/
10+
.modal,
11+
.modal__overlay {
12+
position: fixed;
13+
top: 0;
14+
left: 0;
15+
height: 100vh; /* TODO-AH: make sure this doesn't spill out of the container */
16+
width: 100%;
17+
}
18+
19+
/**
20+
* The inverted background of the modal to provide contrast with the actual modal.
21+
*/
22+
.modal__overlay {
23+
/* TODO-AH: opacity of color based on 50% */
24+
background-color: var(--eds-theme-color-background-utility-overlay-low-emphasis);
25+
opacity: 0.5;
26+
}
27+
28+
/**
29+
* The modal container which positions the modal in the center of the screen.
30+
*/
31+
.modal {
32+
display: flex;
33+
align-items: center;
34+
justify-content: center;
35+
padding: 1rem;
36+
37+
/**
38+
* Ensures modal is above other components. This is not a design token for now since we need to align on
39+
* z-indeces across the system
40+
*/
41+
z-index: 1050;
42+
}
43+
44+
/**
45+
* Modal transition animations.
46+
*/
47+
.modal__transition--enter {
48+
transition: opacity var(--eds-anim-fade-long) var(--eds-anim-ease);
49+
@media (prefers-reduced-motion) {
50+
transition: none;
51+
}
52+
}
53+
54+
.modal__transition--enterFrom {
55+
opacity: 0;
56+
}
57+
58+
.modal__transition--enterTo {
59+
opacity: 1;
60+
}
61+
62+
.modal__transition--leave {
63+
transition: opacity var(--eds-anim-fade-long) var(--eds-anim-ease);
64+
@media (prefers-reduced-motion) {
65+
transition: none;
66+
}
67+
}
68+
69+
.modal__transition--leaveFrom {
70+
opacity: 1;
71+
}
72+
73+
.modal__transition--leaveTo {
74+
opacity: 0;
75+
}
76+
77+
/**
78+
* The content of the modal, which can wrap header, body, and footer.
79+
*/
80+
.modal__content {
81+
position: relative;
82+
height: 43.125rem;
83+
max-height: 90vh;
84+
overflow: hidden;
85+
86+
/**
87+
* This transparent border is for Window High Contrast Mode, which removes all
88+
* background colors but makes borders 100% opacity black. Without this, the
89+
* modal would have no clear boundary.
90+
*/
91+
border: var(--eds-theme-form-border-width) transparent var(--eds-theme-color-background-utility-container);
92+
93+
display: flex;
94+
flex-direction: column;
95+
96+
width: 22.5rem;
97+
98+
background-color: var(--eds-theme-color-background-utility-container);
99+
}
100+
101+
/**
102+
* The medium modal size used for the md modal.
103+
* Also used for the lg modal size for when the screen size is smaller than 75rem.
104+
*/
105+
.modal__content--md {
106+
@media all and (min-width: $eds-bp-md) {
107+
width: 42rem;
108+
}
109+
}
110+
111+
/**
112+
* The large modal size used for the lg/default modal.
113+
*/
114+
.modal__content--lg {
115+
@media all and (min-width: $eds-bp-xl) {
116+
width: 64rem;
117+
--modal-horizontal-padding: 4rem;
118+
}
119+
}
120+
121+
/**
122+
* Allows scrolling of the modal content except for sticky footer.
123+
* This functionality is our intended scroll behavior but consuming teams can
124+
* style the body content as they wish to handle overflow in various ways.
125+
*/
126+
.modal__content--scrollable {
127+
overflow: auto;
128+
}
129+
130+
/**
131+
* The modal close button.
132+
* TODO-AH: this should be a `Button`
133+
*/
134+
.modal__close-button {
135+
border: 0;
136+
background-color: transparent;
137+
138+
position: absolute;
139+
top: 0;
140+
right: 0;
141+
142+
width: 3rem;
143+
height: 3rem;
144+
145+
cursor: pointer;
146+
147+
z-index: 1;
148+
149+
color: var(--eds-theme-color-text-utility-default-secondary);
150+
}
151+
152+
/**
153+
* The modal close icon that resides in the close button.
154+
*/
155+
.modal__close-icon {
156+
position: absolute;
157+
top: 0.5rem;
158+
right: 0.5rem;
159+
}
160+
161+
/*------------------------------------*\
162+
# MODAL BODY
163+
\*------------------------------------*/
164+
165+
/**
166+
* The body of the modal
167+
*/
168+
.modal-body {
169+
flex: 1;
170+
padding: 0 2rem;
171+
}
172+
173+
/*------------------------------------*\
174+
# MODAL FOOTER
175+
\*------------------------------------*/
176+
177+
/**
178+
* Footer for the modal.
179+
*/
180+
.modal-footer {
181+
width: 100%;
182+
z-index: 1000;
183+
184+
padding: 1.5rem 2rem;
185+
186+
background-color: var(--eds-theme-color-background-utility-container);
187+
}
188+
189+
.modal-footer--sticky {
190+
position: sticky;
191+
bottom: 0;
192+
193+
/* TODO-AH: bring in scrollwrapper to handle show/hide of visible elevation (and which box shadow to apply) */
194+
box-shadow: var(--eds-box-shadow-xl);
195+
}
196+
197+
/*------------------------------------*\
198+
# MODAL HEADER
199+
\*------------------------------------*/
200+
201+
/**
202+
* Header for the modal.
203+
*/
204+
.modal-header {
205+
width: 100%;
206+
207+
padding: 1.5rem 2rem;
208+
}
209+
210+
.modal-sub-title {
211+
color: var(--eds-theme-color-text-utility-default-secondary);
212+
}

0 commit comments

Comments
 (0)