@kaizen/components 2.2.4 → 2.3.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.
Files changed (37) hide show
  1. package/dist/cjs/src/MenuV1/index.cjs +4 -3
  2. package/dist/cjs/src/TitleBlock/TitleBlock.cjs +26 -36
  3. package/dist/cjs/src/TitleBlock/TitleBlock.module.scss.cjs +3 -0
  4. package/dist/cjs/src/TitleBlock/subcomponents/MainActions.cjs +90 -45
  5. package/dist/cjs/src/TitleBlock/subcomponents/MainActions.module.scss.cjs +3 -1
  6. package/dist/cjs/src/TitleBlock/subcomponents/SecondaryActions.cjs +51 -14
  7. package/dist/esm/src/MenuV1/index.mjs +5 -3
  8. package/dist/esm/src/TitleBlock/TitleBlock.mjs +27 -37
  9. package/dist/esm/src/TitleBlock/TitleBlock.module.scss.mjs +3 -0
  10. package/dist/esm/src/TitleBlock/subcomponents/MainActions.mjs +92 -47
  11. package/dist/esm/src/TitleBlock/subcomponents/MainActions.module.scss.mjs +3 -1
  12. package/dist/esm/src/TitleBlock/subcomponents/SecondaryActions.mjs +51 -14
  13. package/dist/styles.css +51 -201
  14. package/dist/types/TitleBlock/TitleBlock.d.ts +1 -1
  15. package/dist/types/TitleBlock/subcomponents/MainActions.d.ts +4 -3
  16. package/package.json +1 -1
  17. package/src/TitleBlock/TitleBlock.module.scss +28 -10
  18. package/src/TitleBlock/TitleBlock.spec.tsx +33 -461
  19. package/src/TitleBlock/TitleBlock.tsx +4 -24
  20. package/src/TitleBlock/_docs/TitleBlock.stories.tsx +25 -5
  21. package/src/TitleBlock/_mixins.scss +6 -0
  22. package/src/TitleBlock/subcomponents/MainActions.module.scss +27 -2
  23. package/src/TitleBlock/subcomponents/MainActions.tsx +127 -70
  24. package/src/TitleBlock/subcomponents/SecondaryActions.tsx +105 -45
  25. package/src/TitleBlock/subcomponents/Toolbar.tsx +1 -0
  26. package/dist/cjs/src/MenuV1/subcomponents/MenuHeading/MenuHeading.cjs +0 -27
  27. package/dist/cjs/src/MenuV1/subcomponents/MenuHeading/MenuHeading.module.scss.cjs +0 -6
  28. package/dist/cjs/src/TitleBlock/subcomponents/MobileActions.cjs +0 -306
  29. package/dist/cjs/src/TitleBlock/subcomponents/MobileActions.module.scss.cjs +0 -16
  30. package/dist/esm/src/MenuV1/subcomponents/MenuHeading/MenuHeading.mjs +0 -21
  31. package/dist/esm/src/MenuV1/subcomponents/MenuHeading/MenuHeading.module.scss.mjs +0 -4
  32. package/dist/esm/src/TitleBlock/subcomponents/MobileActions.mjs +0 -300
  33. package/dist/esm/src/TitleBlock/subcomponents/MobileActions.module.scss.mjs +0 -14
  34. package/dist/types/TitleBlock/subcomponents/MobileActions.d.ts +0 -14
  35. package/src/TitleBlock/subcomponents/MobileActions.module.scss +0 -208
  36. package/src/TitleBlock/subcomponents/MobileActions.spec.tsx +0 -210
  37. package/src/TitleBlock/subcomponents/MobileActions.tsx +0 -472
@@ -1,472 +0,0 @@
1
- import React, { useCallback, useEffect, useState } from 'react'
2
- import classnames from 'classnames'
3
- import { FocusOn } from 'react-focus-on'
4
- import type { ButtonProps } from '~components/Button'
5
- import { Icon } from '~components/Icon'
6
- import { MenuHeading, MenuItem, MenuList } from '~components/MenuV1'
7
- import { TITLE_BLOCK_ZEN_OTHER_ACTIONS_HTML_ID } from '../constants'
8
- import {
9
- type DefaultActionProps,
10
- type PrimaryActionProps,
11
- type SecondaryActionsProps,
12
- type TitleBlockButtonProps,
13
- type TitleBlockMenuGroup,
14
- type TitleBlockMenuItemProps,
15
- } from '../types'
16
- import { convertSecondaryActionsToMenuItems, isMenuGroupNotButton } from '../utils'
17
- import { TitleBlockMenuItem } from './TitleBlockMenuItem'
18
-
19
- import styles from './MobileActions.module.scss'
20
-
21
- const menuItemIsLink: (item: TitleBlockMenuItemProps) => boolean = (item) => 'href' in item
22
-
23
- const defaultActionIsLink: (action: DefaultActionProps) => boolean = (action) => 'href' in action
24
-
25
- const defaultActionIsButton: (action: DefaultActionProps) => boolean = (action) =>
26
- (!('href' in action) && 'onClick' in action) || 'component' in action
27
-
28
- const filterActions = (
29
- menuItems: TitleBlockMenuItemProps[],
30
- filterType: 'link' | 'action',
31
- ): TitleBlockMenuItemProps[] =>
32
- menuItems.filter((item) => (filterType === 'link' ? menuItemIsLink(item) : !menuItemIsLink(item)))
33
-
34
- /** Returns a filtered array of TitleBlockMenuItem based on actionType
35
- * This is use to sort a selectively render the action into a specifc order
36
- */
37
- const renderPrimaryActionDrawerContent = (
38
- primaryAction: PrimaryActionProps,
39
- actionType: 'link' | 'action',
40
- ): JSX.Element[] | null => {
41
- if (!primaryAction) return null
42
-
43
- if (isMenuGroupNotButton(primaryAction)) {
44
- const filteredActions = filterActions(primaryAction.menuItems, actionType)
45
- return filteredActions.map((item, idx) => {
46
- const itemType = menuItemIsLink(item) ? 'link' : 'action'
47
-
48
- return (
49
- <TitleBlockMenuItem
50
- {...item}
51
- key={`title-block-mobile-actions-primary-${itemType}-${idx}`}
52
- data-automation-id={`title-block-mobile-actions-primary-${itemType}-${idx}`}
53
- data-testid={`title-block-mobile-actions-primary-${itemType}-${idx}`}
54
- />
55
- )
56
- })
57
- }
58
-
59
- return null
60
- }
61
-
62
- const renderDefaultLink = (defaultAction: DefaultActionProps): JSX.Element | undefined => {
63
- if (!defaultActionIsLink(defaultAction)) return
64
- if ('component' in defaultAction) {
65
- return (
66
- <TitleBlockMenuItem
67
- {...defaultAction}
68
- key="title-block-mobile-actions-default-link"
69
- data-automation-id="title-block-mobile-actions-default-link"
70
- data-testid="title-block-mobile-actions-default-link"
71
- />
72
- )
73
- }
74
- return (
75
- <MenuItem
76
- href={defaultAction.href}
77
- label={defaultAction.label}
78
- icon={defaultAction.icon}
79
- disabled={defaultAction.disabled}
80
- key="title-block-mobile-actions-default-link"
81
- data-automation-id="title-block-mobile-actions-default-link"
82
- data-testid="title-block-mobile-actions-default-link"
83
- id={defaultAction.id}
84
- />
85
- )
86
- }
87
-
88
- const renderDefaultAction = (defaultAction: DefaultActionProps): JSX.Element | null => {
89
- if (!defaultActionIsLink(defaultAction)) {
90
- return (
91
- <TitleBlockMenuItem
92
- {...defaultAction}
93
- key="title-block-mobile-actions-default-action"
94
- data-automation-id="title-block-mobile-actions-default-action"
95
- data-testid="title-block-mobile-actions-default-action"
96
- />
97
- )
98
- }
99
-
100
- return null
101
- }
102
-
103
- const renderSecondaryActions = (
104
- secondaryActions: SecondaryActionsProps | undefined,
105
- ): JSX.Element[] | null => {
106
- if (!secondaryActions) return null
107
- const secondaryActionMenuItems: TitleBlockMenuItemProps[] =
108
- convertSecondaryActionsToMenuItems(secondaryActions)
109
-
110
- return secondaryActionMenuItems.map((item, idx) => (
111
- <TitleBlockMenuItem
112
- {...item}
113
- key={`title-block-mobile-actions-secondary-action-${idx}`}
114
- data-testid="title-block-mobile-actions-secondary-action"
115
- />
116
- ))
117
- }
118
-
119
- const renderSecondaryOverflowMenuItems = (
120
- secondaryOverflowMenuItems: TitleBlockMenuItemProps[],
121
- ): JSX.Element[] =>
122
- secondaryOverflowMenuItems.map((item, idx) => (
123
- <TitleBlockMenuItem
124
- {...item}
125
- key={`title-block-mobile-actions-overflow-menu-item-${idx}`}
126
- data-testid="title-block-mobile-actions-overflow-menu-item"
127
- />
128
- ))
129
-
130
- type DrawerMenuContentProps = {
131
- primaryAction?: PrimaryActionProps
132
- defaultAction?: DefaultActionProps
133
- secondaryActions?: SecondaryActionsProps
134
- secondaryOverflowMenuItems?: TitleBlockMenuItemProps[]
135
- }
136
-
137
- const DrawerMenuContent = ({
138
- primaryAction,
139
- defaultAction,
140
- secondaryActions,
141
- secondaryOverflowMenuItems,
142
- }: DrawerMenuContentProps): JSX.Element => {
143
- const showOtherActionsHeading =
144
- (defaultAction && defaultActionIsButton(defaultAction)) ??
145
- secondaryActions ??
146
- secondaryOverflowMenuItems
147
-
148
- return (
149
- <>
150
- <MenuList>
151
- {primaryAction && renderPrimaryActionDrawerContent(primaryAction, 'link')}
152
- {defaultAction && renderDefaultLink(defaultAction)}
153
- {primaryAction && renderPrimaryActionDrawerContent(primaryAction, 'action')}
154
- </MenuList>
155
- {(defaultAction ?? secondaryActions ?? secondaryOverflowMenuItems) && (
156
- <MenuList
157
- heading={showOtherActionsHeading ? <MenuHeading>Other actions</MenuHeading> : undefined}
158
- >
159
- {defaultAction && renderDefaultAction(defaultAction)}
160
- {secondaryActions && renderSecondaryActions(secondaryActions)}
161
- {secondaryOverflowMenuItems &&
162
- renderSecondaryOverflowMenuItems(secondaryOverflowMenuItems)}
163
- </MenuList>
164
- )}
165
- </>
166
- )
167
- }
168
-
169
- const renderDrawerHandleLabel = (
170
- label: string,
171
- icon?: JSX.Element,
172
- drawerHandleLabelIconPosition?: ButtonProps['iconPosition'],
173
- ): JSX.Element => {
174
- if (drawerHandleLabelIconPosition === 'end') {
175
- return (
176
- <>
177
- <span className={styles.drawerHandleLabelText} data-testid="drawer-handle-lable-text">
178
- {label}
179
- </span>
180
- <>{icon && <span className={styles.drawerHandleIcon}>{icon}</span>}</>
181
- </>
182
- )
183
- } else {
184
- return (
185
- <>
186
- <>{icon && <span className={styles.drawerHandleIcon}>{icon}</span>}</>
187
- <span className={styles.drawerHandleLabelText} data-testid="drawer-handle-lable-text">
188
- {label}
189
- </span>
190
- </>
191
- )
192
- }
193
- }
194
-
195
- type HrefAndOnClick = Pick<TitleBlockButtonProps, 'href' | 'onClick'>
196
- type ButtonOrLinkActionProps =
197
- | HrefAndOnClick
198
- | TitleBlockButtonProps['href']
199
- | TitleBlockButtonProps['onClick']
200
- type ButtonOrLinkProps = {
201
- action?: ButtonOrLinkActionProps
202
- children: React.ReactNode
203
- }
204
-
205
- const ButtonOrLink = ({ action, children }: ButtonOrLinkProps): JSX.Element => {
206
- if (typeof action === 'object' && 'onClick' in action && 'href' in action) {
207
- return (
208
- <a
209
- onClick={action.onClick}
210
- href={action.href}
211
- className={classnames(styles.mobileActionsPrimaryLabel, styles.mobileActionsPrimaryButton)}
212
- data-testid="title-block-mobile-actions-primary-button"
213
- >
214
- {children}
215
- </a>
216
- )
217
- }
218
- if (typeof action === 'function') {
219
- return (
220
- <button
221
- type="button"
222
- onClick={action}
223
- className={classnames(styles.mobileActionsPrimaryLabel, styles.mobileActionsPrimaryButton)}
224
- data-testid="title-block-mobile-actions-primary-button"
225
- >
226
- {children}
227
- </button>
228
- )
229
- }
230
- if (typeof action === 'string') {
231
- return (
232
- <a
233
- href={action}
234
- className={classnames(styles.mobileActionsPrimaryLabel, styles.mobileActionsPrimaryButton)}
235
- data-testid="title-block-mobile-actions-primary-button"
236
- >
237
- {children}
238
- </a>
239
- )
240
- }
241
-
242
- // when there's no action (e.g. primary button is disabled)
243
- return (
244
- <button
245
- type="button"
246
- className={classnames(styles.mobileActionsPrimaryLabel, styles.mobileActionsPrimaryButton)}
247
- data-testid="title-block-mobile-actions-primary-button"
248
- >
249
- {children}
250
- </button>
251
- )
252
- }
253
-
254
- const getAction = (primaryAction: TitleBlockButtonProps): ButtonOrLinkActionProps => {
255
- if (primaryAction && !primaryAction.disabled) {
256
- if (primaryAction.onClick && primaryAction.href) {
257
- return {
258
- href: primaryAction.href,
259
- onClick: primaryAction.onClick,
260
- }
261
- }
262
- if (primaryAction.onClick) {
263
- return primaryAction.onClick
264
- }
265
- if (primaryAction.href) {
266
- return primaryAction.href
267
- }
268
- }
269
-
270
- return undefined
271
- }
272
-
273
- type DrawerHandleProps = {
274
- primaryAction: PrimaryActionProps | undefined
275
- secondaryActions: SecondaryActionsProps | undefined
276
- defaultAction?: DefaultActionProps | TitleBlockMenuGroup
277
- secondaryOverflowMenuItems?: TitleBlockMenuItemProps[]
278
- drawerHandleLabelIconPosition?: ButtonProps['iconPosition']
279
- toggleDisplay: () => void
280
- isOpen: boolean
281
- }
282
-
283
- const DrawerHandle = ({
284
- primaryAction,
285
- secondaryActions,
286
- defaultAction,
287
- secondaryOverflowMenuItems,
288
- drawerHandleLabelIconPosition,
289
- toggleDisplay,
290
- isOpen,
291
- }: DrawerHandleProps): JSX.Element | null => {
292
- const showDrawer = defaultAction ?? secondaryActions ?? secondaryOverflowMenuItems
293
- if (primaryAction) {
294
- // If the primary action is a menu
295
- if (isMenuGroupNotButton(primaryAction)) {
296
- return (
297
- <div
298
- className={classnames(styles.mobileActionsTopRow, styles.mobileActionsTopRowSingleButton)}
299
- data-testid="title-block-mobile-actions-drawer-handle"
300
- >
301
- <button
302
- type="button"
303
- className={classnames(
304
- styles.mobileActionsExpandButton,
305
- styles.mobileActionsPrimaryLabel,
306
- )}
307
- onClick={toggleDisplay}
308
- aria-expanded={isOpen}
309
- >
310
- {primaryAction.label}
311
- <span className={styles.mobileActionsChevronSquare}>
312
- <Icon name={isOpen ? 'keyboard_arrow_down' : 'keyboard_arrow_up'} isPresentational />
313
- </span>
314
- </button>
315
- </div>
316
- )
317
- }
318
-
319
- // If the primary action is a button, or has no onClick/href/action
320
- return (
321
- <div
322
- className={classnames(
323
- styles.mobileActionsTopRow,
324
- !showDrawer && styles.mobileActionsTopRowSingleButton,
325
- )}
326
- data-testid="title-block-mobile-actions-drawer-handle"
327
- >
328
- {'component' in primaryAction ? (
329
- <primaryAction.component
330
- className={classnames(
331
- styles.mobileActionsPrimaryLabel,
332
- styles.mobileActionsPrimaryButton,
333
- )}
334
- {...primaryAction}
335
- >
336
- {primaryAction.label &&
337
- renderDrawerHandleLabel(
338
- primaryAction.label,
339
- primaryAction.icon,
340
- drawerHandleLabelIconPosition,
341
- )}
342
- </primaryAction.component>
343
- ) : (
344
- <ButtonOrLink action={getAction(primaryAction)}>
345
- {renderDrawerHandleLabel(
346
- primaryAction.label,
347
- primaryAction.icon,
348
- drawerHandleLabelIconPosition,
349
- )}
350
- </ButtonOrLink>
351
- )}
352
-
353
- {/* If there are no secondary etc. actions, just show the button without drawer */}
354
- {showDrawer && (
355
- <button
356
- type="button"
357
- className={styles.mobileActionsExpandButton}
358
- onClick={toggleDisplay}
359
- aria-expanded={isOpen}
360
- id={TITLE_BLOCK_ZEN_OTHER_ACTIONS_HTML_ID}
361
- aria-label="Other actions"
362
- >
363
- <Icon name={isOpen ? 'keyboard_arrow_down' : 'keyboard_arrow_up'} isPresentational />
364
- </button>
365
- )}
366
- </div>
367
- )
368
- }
369
-
370
- // if there are default/secondary actions but no primary action
371
- if (showDrawer) {
372
- return (
373
- <div
374
- className={classnames(styles.mobileActionsTopRow, styles.mobileActionsTopRowSingleButton)}
375
- data-testid="title-block-mobile-actions-drawer-handle"
376
- >
377
- <button
378
- type="button"
379
- className={classnames(styles.mobileActionsExpandButton, styles.mobileActionsPrimaryLabel)}
380
- onClick={toggleDisplay}
381
- aria-expanded={isOpen}
382
- id={TITLE_BLOCK_ZEN_OTHER_ACTIONS_HTML_ID}
383
- >
384
- {renderDrawerHandleLabel('Other actions')}
385
- <span className={styles.mobileActionsChevronSquare}>
386
- <Icon name={isOpen ? 'keyboard_arrow_down' : 'keyboard_arrow_up'} isPresentational />
387
- </span>
388
- </button>
389
- </div>
390
- )
391
- }
392
- return null
393
- }
394
-
395
- export type MobileActionsProps = {
396
- primaryAction?: PrimaryActionProps
397
- defaultAction?: DefaultActionProps
398
- secondaryActions?: SecondaryActionsProps
399
- secondaryOverflowMenuItems?: TitleBlockMenuItemProps[]
400
- drawerHandleLabelIconPosition?: ButtonProps['iconPosition']
401
- autoHide?: boolean
402
- }
403
-
404
- export const MobileActions = ({
405
- primaryAction,
406
- defaultAction,
407
- secondaryActions,
408
- secondaryOverflowMenuItems,
409
- drawerHandleLabelIconPosition,
410
- autoHide = false,
411
- }: MobileActionsProps): JSX.Element => {
412
- const [isOpen, setIsOpen] = useState<boolean>(false)
413
- const menuContent = React.createRef<HTMLDivElement>()
414
- const toggleDisplay = (): void => {
415
- setIsOpen(!isOpen)
416
- }
417
-
418
- // This callback handler will not run when autoHide === "off"
419
- const handleDocumentClickForAutoHide = useCallback(
420
- (e: MouseEvent) => {
421
- if (isOpen && e.target instanceof Node && menuContent.current?.contains(e.target)) {
422
- setIsOpen(false)
423
- }
424
- },
425
- // @todo: Fix if possible - avoiding breaking in eslint upgrade
426
- // eslint-disable-next-line react-hooks/exhaustive-deps
427
- [menuContent],
428
- )
429
-
430
- useEffect(() => {
431
- if (autoHide) {
432
- document.addEventListener('click', handleDocumentClickForAutoHide, true)
433
- }
434
-
435
- return () => {
436
- if (autoHide) {
437
- document.removeEventListener('click', handleDocumentClickForAutoHide, true)
438
- }
439
- }
440
- }, [autoHide, handleDocumentClickForAutoHide])
441
-
442
- return (
443
- <div className={classnames(styles.mobileActionsContainer, isOpen && styles.isOpen)}>
444
- <FocusOn enabled={isOpen} scrollLock={false}>
445
- <DrawerHandle
446
- primaryAction={primaryAction}
447
- secondaryActions={secondaryActions}
448
- defaultAction={defaultAction}
449
- secondaryOverflowMenuItems={secondaryOverflowMenuItems}
450
- drawerHandleLabelIconPosition={drawerHandleLabelIconPosition}
451
- toggleDisplay={toggleDisplay}
452
- isOpen={isOpen}
453
- />
454
- {(defaultAction ??
455
- secondaryActions ??
456
- secondaryOverflowMenuItems ??
457
- (primaryAction && isMenuGroupNotButton(primaryAction))) && (
458
- <div ref={menuContent} className={styles.mobileActionsMenuContainer}>
459
- <DrawerMenuContent
460
- primaryAction={primaryAction}
461
- defaultAction={defaultAction}
462
- secondaryActions={secondaryActions}
463
- secondaryOverflowMenuItems={secondaryOverflowMenuItems}
464
- />
465
- </div>
466
- )}
467
- </FocusOn>
468
- </div>
469
- )
470
- }
471
-
472
- MobileActions.displayName = 'MobileActions'