@hyphen/hyphen-components 2.25.2 → 4.0.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.
- package/dist/components/Button/Button.d.ts +12 -51
- package/dist/components/Button/Button.stories.d.ts +1 -1
- package/dist/components/Drawer/Drawer.d.ts +30 -13
- package/dist/components/Drawer/Drawer.stories.d.ts +56 -11
- package/dist/components/Sidebar/Sidebar.d.ts +2 -6
- package/dist/css/index.css +2 -2
- package/dist/hyphen-components.cjs.development.js +289 -220
- package/dist/hyphen-components.cjs.development.js.map +1 -1
- package/dist/hyphen-components.cjs.production.min.js +1 -1
- package/dist/hyphen-components.cjs.production.min.js.map +1 -1
- package/dist/hyphen-components.esm.js +284 -222
- package/dist/hyphen-components.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Button/Button.mdx +7 -7
- package/src/components/Button/Button.module.scss +3 -0
- package/src/components/Button/Button.stories.tsx +12 -12
- package/src/components/Button/Button.test.tsx +128 -112
- package/src/components/Button/Button.tsx +80 -178
- package/src/components/Drawer/Drawer.mdx +9 -24
- package/src/components/Drawer/Drawer.module.scss +4 -3
- package/src/components/Drawer/Drawer.stories.tsx +255 -296
- package/src/components/Drawer/Drawer.test.tsx +141 -71
- package/src/components/Drawer/Drawer.tsx +241 -77
- package/src/components/Sidebar/Sidebar.stories.tsx +4 -4
- package/src/lib/tokens.ts +11 -8
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
import { BoxShadowSize, IconName, ResponsiveProp } from '../../types';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
ButtonHTMLAttributes,
|
|
5
|
-
FocusEvent,
|
|
6
|
-
MouseEvent,
|
|
7
|
-
ReactNode,
|
|
8
|
-
createElement,
|
|
9
|
-
forwardRef,
|
|
10
|
-
} from 'react';
|
|
2
|
+
import { Slot, Slottable } from '@radix-ui/react-slot';
|
|
3
|
+
import React, { ButtonHTMLAttributes, forwardRef } from 'react';
|
|
11
4
|
|
|
12
|
-
import { Box } from '../Box/Box';
|
|
13
5
|
import { Icon } from '../Icon/Icon';
|
|
14
6
|
import { Spinner } from '../Spinner/Spinner';
|
|
15
7
|
import classNames from 'classnames';
|
|
16
8
|
import { generateResponsiveClasses } from '../../lib/generateResponsiveClasses';
|
|
17
|
-
import { getElementType } from '../../lib/getElementType';
|
|
18
|
-
import { handleReactRouterClick } from '../../lib/reactRouterClickHandler';
|
|
19
9
|
import styles from './Button.module.scss';
|
|
20
10
|
|
|
21
11
|
export type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'danger';
|
|
@@ -24,9 +14,9 @@ export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
|
24
14
|
|
|
25
15
|
export interface BaseButtonProps {
|
|
26
16
|
/**
|
|
27
|
-
*
|
|
17
|
+
* The button element to render as. Useful for when you want to render a Link that looks like a button.
|
|
28
18
|
*/
|
|
29
|
-
|
|
19
|
+
asChild?: boolean;
|
|
30
20
|
/**
|
|
31
21
|
* Additional ClassNames to add to button.
|
|
32
22
|
*/
|
|
@@ -36,116 +26,54 @@ export interface BaseButtonProps {
|
|
|
36
26
|
*/
|
|
37
27
|
fullWidth?: boolean;
|
|
38
28
|
/**
|
|
39
|
-
*
|
|
29
|
+
* Icon displayed before the button label
|
|
40
30
|
*/
|
|
41
31
|
iconPrefix?: IconName;
|
|
42
32
|
/**
|
|
43
|
-
*
|
|
33
|
+
* Icon displayed after the button label
|
|
44
34
|
*/
|
|
45
35
|
iconSuffix?: IconName;
|
|
46
36
|
/**
|
|
47
|
-
*
|
|
48
|
-
*/
|
|
49
|
-
id?: string;
|
|
50
|
-
/**
|
|
51
|
-
* URL to navigate to when clicked. Passing this attribute automatically
|
|
52
|
-
* renders an anchor <a> tag, NOT a <button> element.
|
|
53
|
-
*/
|
|
54
|
-
href?: string;
|
|
55
|
-
/**
|
|
56
|
-
* Disables the button, making it inoperable.
|
|
37
|
+
* Disables the button
|
|
57
38
|
*/
|
|
58
39
|
isDisabled?: boolean;
|
|
59
40
|
/**
|
|
60
|
-
*
|
|
41
|
+
* Displays a loading spinner and disables the button
|
|
61
42
|
*/
|
|
62
43
|
isLoading?: boolean;
|
|
63
44
|
/**
|
|
64
|
-
*
|
|
65
|
-
*/
|
|
66
|
-
navigate?: () => void;
|
|
67
|
-
/**
|
|
68
|
-
* Callback when Button is pressed.
|
|
69
|
-
*/
|
|
70
|
-
onClick?: (event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
|
|
71
|
-
/**
|
|
72
|
-
* Callback when focus leaves Button.
|
|
73
|
-
*/
|
|
74
|
-
onBlur?: (event: FocusEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
|
|
75
|
-
/**
|
|
76
|
-
* Callback when Button receives focus.
|
|
77
|
-
*/
|
|
78
|
-
onFocus?: (event: FocusEvent<HTMLButtonElement | HTMLAnchorElement>) => void;
|
|
79
|
-
/**
|
|
80
|
-
* The size of the drop shadow applied to the Box
|
|
45
|
+
* Size of the drop shadow applied to the Box
|
|
81
46
|
*/
|
|
82
47
|
shadow?: BoxShadowSize | ResponsiveProp<BoxShadowSize>;
|
|
83
48
|
/**
|
|
84
|
-
*
|
|
85
|
-
*/
|
|
86
|
-
tabIndex?: number;
|
|
87
|
-
/**
|
|
88
|
-
* Useful when using button as an anchor tag.
|
|
89
|
-
*/
|
|
90
|
-
target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
|
|
91
|
-
/**
|
|
92
|
-
* The size of the button.
|
|
49
|
+
* Size of the button.
|
|
93
50
|
*/
|
|
94
51
|
size?: ButtonSize | ResponsiveProp<ButtonSize>;
|
|
95
52
|
/**
|
|
96
|
-
*
|
|
53
|
+
* Visual variant of the button
|
|
97
54
|
*/
|
|
98
55
|
variant?: ButtonVariant;
|
|
99
|
-
/**
|
|
100
|
-
* ref - currently cannot be typed due to limitations of using the `as` prop
|
|
101
|
-
*/
|
|
102
56
|
}
|
|
103
57
|
|
|
104
|
-
export type
|
|
105
|
-
|
|
106
|
-
React.DetailedHTMLProps<
|
|
107
|
-
AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
108
|
-
HTMLAnchorElement
|
|
109
|
-
>,
|
|
110
|
-
'ref'
|
|
111
|
-
>;
|
|
112
|
-
|
|
113
|
-
export type NormalButtonProps = { as?: 'button' } & BaseButtonProps &
|
|
114
|
-
Omit<
|
|
115
|
-
React.DetailedHTMLProps<
|
|
116
|
-
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
117
|
-
HTMLButtonElement
|
|
118
|
-
>,
|
|
119
|
-
'ref'
|
|
120
|
-
>;
|
|
58
|
+
export type ButtonMergedProps = BaseButtonProps &
|
|
59
|
+
ButtonHTMLAttributes<HTMLButtonElement>;
|
|
121
60
|
|
|
122
|
-
export
|
|
123
|
-
|
|
124
|
-
export const Button = forwardRef<
|
|
125
|
-
HTMLAnchorElement | HTMLButtonElement,
|
|
126
|
-
ButtonProps
|
|
127
|
-
>(
|
|
61
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonMergedProps>(
|
|
128
62
|
(
|
|
129
63
|
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
className
|
|
133
|
-
fullWidth
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
onFocus = undefined,
|
|
143
|
-
onBlur = undefined,
|
|
144
|
-
shadow = undefined,
|
|
64
|
+
asChild,
|
|
65
|
+
children,
|
|
66
|
+
className,
|
|
67
|
+
fullWidth,
|
|
68
|
+
iconPrefix,
|
|
69
|
+
iconSuffix,
|
|
70
|
+
isDisabled,
|
|
71
|
+
isLoading,
|
|
72
|
+
onClick,
|
|
73
|
+
onBlur,
|
|
74
|
+
onFocus,
|
|
75
|
+
shadow,
|
|
145
76
|
size = 'md',
|
|
146
|
-
tabIndex = undefined,
|
|
147
|
-
target = undefined,
|
|
148
|
-
type = undefined,
|
|
149
77
|
variant = 'primary',
|
|
150
78
|
...restProps
|
|
151
79
|
},
|
|
@@ -153,9 +81,9 @@ export const Button = forwardRef<
|
|
|
153
81
|
) => {
|
|
154
82
|
const disabled = isLoading || isDisabled;
|
|
155
83
|
|
|
156
|
-
const responsiveClasses = generateResponsiveClasses('size', size)
|
|
157
|
-
(c) => styles[c]
|
|
158
|
-
|
|
84
|
+
const responsiveClasses = generateResponsiveClasses('size', size)
|
|
85
|
+
.map((c) => styles[c])
|
|
86
|
+
.filter(Boolean);
|
|
159
87
|
|
|
160
88
|
const buttonClasses = classNames(
|
|
161
89
|
'hyphen-components__variables__form-control',
|
|
@@ -170,83 +98,57 @@ export const Button = forwardRef<
|
|
|
170
98
|
}
|
|
171
99
|
);
|
|
172
100
|
|
|
173
|
-
const handleClick =
|
|
174
|
-
|
|
175
|
-
const handleFocus =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
</>
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
const buttonElement = getElementType(Button, { as });
|
|
226
|
-
|
|
227
|
-
return createElement(
|
|
228
|
-
buttonElement,
|
|
229
|
-
{
|
|
230
|
-
['aria-disabled']: disabled,
|
|
231
|
-
id,
|
|
232
|
-
href,
|
|
233
|
-
className: buttonClasses,
|
|
234
|
-
disabled,
|
|
235
|
-
target: as === 'a' && href ? target : null,
|
|
236
|
-
rel:
|
|
237
|
-
as === 'a' && href && target === '_blank'
|
|
238
|
-
? 'noopener noreferrer'
|
|
239
|
-
: null,
|
|
240
|
-
onBlur: handleBlur,
|
|
241
|
-
onClick: (event: MouseEvent<HTMLAnchorElement | HTMLButtonElement>) =>
|
|
242
|
-
handleClick(event, onClick, target, navigate),
|
|
243
|
-
onFocus: handleFocus,
|
|
244
|
-
ref,
|
|
245
|
-
type: type || (as !== 'a' && !href ? 'button' : undefined),
|
|
246
|
-
tabIndex,
|
|
247
|
-
...restProps,
|
|
248
|
-
},
|
|
249
|
-
buttonContent
|
|
101
|
+
const handleClick = !disabled ? onClick : undefined;
|
|
102
|
+
const handleBlur = !disabled ? onBlur : undefined;
|
|
103
|
+
const handleFocus = !disabled ? onFocus : undefined;
|
|
104
|
+
|
|
105
|
+
const Comp = asChild ? Slot : 'button';
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<Comp
|
|
109
|
+
{...(disabled && { 'aria-disabled': true })}
|
|
110
|
+
disabled={disabled}
|
|
111
|
+
className={buttonClasses}
|
|
112
|
+
onClick={handleClick}
|
|
113
|
+
onBlur={handleBlur}
|
|
114
|
+
onFocus={handleFocus}
|
|
115
|
+
ref={ref}
|
|
116
|
+
{...restProps}
|
|
117
|
+
>
|
|
118
|
+
{isLoading && <Spinner className={styles['spinner-wrapper']} />}
|
|
119
|
+
{iconPrefix && (
|
|
120
|
+
<Icon
|
|
121
|
+
className={styles.label}
|
|
122
|
+
name={iconPrefix}
|
|
123
|
+
aria-hidden="true"
|
|
124
|
+
focusable="false"
|
|
125
|
+
data-testid="prefixIcon"
|
|
126
|
+
size={size === 'md' ? 'sm' : size}
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
129
|
+
{children && (
|
|
130
|
+
<Slottable>
|
|
131
|
+
{asChild ? (
|
|
132
|
+
children
|
|
133
|
+
) : (
|
|
134
|
+
<span className={styles.label}>{children}</span>
|
|
135
|
+
)}
|
|
136
|
+
</Slottable>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
{iconSuffix && (
|
|
140
|
+
<Icon
|
|
141
|
+
className={styles.label}
|
|
142
|
+
name={iconSuffix}
|
|
143
|
+
aria-hidden="true"
|
|
144
|
+
focusable="false"
|
|
145
|
+
data-testid="suffixIcon"
|
|
146
|
+
size={size === 'md' ? 'sm' : size}
|
|
147
|
+
/>
|
|
148
|
+
)}
|
|
149
|
+
</Comp>
|
|
250
150
|
);
|
|
251
151
|
}
|
|
252
152
|
);
|
|
153
|
+
|
|
154
|
+
Button.displayName = 'Button';
|
|
@@ -9,12 +9,14 @@ import * as Stories from './Drawer.stories';
|
|
|
9
9
|
|
|
10
10
|
A Drawer is a panel that slides in from one edge of the viewport and overlays content on top of the page. It contains information or actions without leaving the context of the original page.
|
|
11
11
|
|
|
12
|
-
<Canvas withSource="open" of={Stories.
|
|
12
|
+
<Canvas withSource="open" of={Stories.UncontrolledWithProvider} />
|
|
13
13
|
|
|
14
14
|
## Usage Guidelines
|
|
15
15
|
|
|
16
16
|
- Use the drawer as way to achieve progressive disclosure, to reveal relevant information at the appropriate time.
|
|
17
|
-
- The Drawer
|
|
17
|
+
- The Drawer can be both uncontrolled and controlled. The uncontrolled Drawer is managed by the component itself, while the controlled Drawer is managed by the consuming component.
|
|
18
|
+
- For an uncontrolled Drawer, use the `DrawerProvider` to manage the Drawer's visibility. You can set a default `defaultIsOpen` value to determine if the Drawer is open by default.
|
|
19
|
+
- For a controlled Drawer, omit the `DrawerProvider` as visibility is controlled via the `isOpen` prop. To handle closing the Drawer, provide an `onDismiss` callback that will be called when the user clicks the Overlay or Esc keyboard key.
|
|
18
20
|
- When a Drawer is open, the main body is scroll-locked by default.
|
|
19
21
|
- Avoid nesting Drawers to prevent usability issues.
|
|
20
22
|
- The button that triggers the drawer opening should be in close proximity to the Drawer itself.
|
|
@@ -36,10 +38,11 @@ Drawers are good for short pieces of content that are related to the main screen
|
|
|
36
38
|
|
|
37
39
|
## Accessibility
|
|
38
40
|
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
+
- Ensure the `ariaLabel` or `ariaLabelledBy` props are provided for accessibility.
|
|
42
|
+
- The drawer traps focus within its content when open, ensuring keyboard navigation is contained.
|
|
41
43
|
- The 'Esc' key will close the Drawer.
|
|
42
44
|
- After the drawer closes, focus is returned to the element that triggered it.
|
|
45
|
+
- The `DrawerCloseButton` component provides a close button with appropriate aria attributes.
|
|
43
46
|
|
|
44
47
|
## Props
|
|
45
48
|
|
|
@@ -51,20 +54,6 @@ The Drawer can appear from the right (default), left, top, or bottom of the scre
|
|
|
51
54
|
|
|
52
55
|
<Canvas withSource="open" of={Stories.Placement} />
|
|
53
56
|
|
|
54
|
-
## Drawer Header
|
|
55
|
-
|
|
56
|
-
A header will be added to the drawer content if `title` is defined, or `closeButton` is `true`. If the content of the drawer is taller than the drawer height, then the content will scroll while the header remains fixed to the top.
|
|
57
|
-
|
|
58
|
-
<Canvas withSource="open" of={Stories.DrawerHeader} />
|
|
59
|
-
|
|
60
|
-
## Title and Close Button
|
|
61
|
-
|
|
62
|
-
<Canvas withSource="open" of={Stories.TitleAndCloseButton} />
|
|
63
|
-
|
|
64
|
-
## Close Button Only
|
|
65
|
-
|
|
66
|
-
<Canvas withSource="open" of={Stories.CloseButtonOnly} />
|
|
67
|
-
|
|
68
57
|
## Width
|
|
69
58
|
|
|
70
59
|
Set the width of a Drawer to specific value for a `left` or `right` placement via `width`. The Drawer height will be 100% of the viewport, or if `containerRef` is used, 100% of the container.
|
|
@@ -73,11 +62,6 @@ When `placement` is set to `top` or `bottom`, the `width` prop is ignored and th
|
|
|
73
62
|
|
|
74
63
|
<Canvas withSource="open" of={Stories.Width} />
|
|
75
64
|
|
|
76
|
-
## Height
|
|
77
|
-
|
|
78
|
-
The height of Drawers with a `top` or `bottom` placement is determined by Drawer's contents. The width will be set to 100%.
|
|
79
|
-
|
|
80
|
-
<Canvas withSource="open" of={Stories.Height} />
|
|
81
65
|
|
|
82
66
|
## Hidden Overlay
|
|
83
67
|
|
|
@@ -92,6 +76,7 @@ In cases where content in the drawer is supplemental to content on the main area
|
|
|
92
76
|
|
|
93
77
|
<Canvas withSource="open" of={Stories.HiddenOverlay} />
|
|
94
78
|
|
|
79
|
+
|
|
95
80
|
## Initial Focus Ref
|
|
96
81
|
|
|
97
82
|
By default the first focusable element will receive focus when the drawer opens, but you can provide a ref to focus instead.
|
|
@@ -114,4 +99,4 @@ Render the Drawer within a containing div using `containerRef`.
|
|
|
114
99
|
variant="info"
|
|
115
100
|
/>
|
|
116
101
|
|
|
117
|
-
<Canvas withSource="open" of={Stories.ContainedDrawer} />
|
|
102
|
+
<Canvas withSource="open" of={Stories.ContainedDrawer} />
|
|
@@ -45,10 +45,11 @@
|
|
|
45
45
|
position: absolute;
|
|
46
46
|
box-shadow: var(--drawer-box-shadow, var(--size-box-shadow-xl));
|
|
47
47
|
z-index: var(--size-z-index-drawer);
|
|
48
|
+
overflow: auto;
|
|
48
49
|
|
|
49
50
|
&.left {
|
|
50
51
|
left: 0;
|
|
51
|
-
width: var(--
|
|
52
|
+
width: var(--drawer-width, 80vw);
|
|
52
53
|
height: 100%;
|
|
53
54
|
|
|
54
55
|
:global {
|
|
@@ -58,7 +59,7 @@
|
|
|
58
59
|
|
|
59
60
|
&.right {
|
|
60
61
|
right: 0;
|
|
61
|
-
width: var(--
|
|
62
|
+
width: var(--drawer-width, 80vw);
|
|
62
63
|
height: 100%;
|
|
63
64
|
|
|
64
65
|
:global {
|
|
@@ -89,7 +90,7 @@
|
|
|
89
90
|
@media (min-width: $size-breakpoint-tablet) {
|
|
90
91
|
&.right,
|
|
91
92
|
&.left {
|
|
92
|
-
width: var(--
|
|
93
|
+
width: var(--drawer-width, var(--size-dimension-8xl));
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
}
|