@kaizen/components 2.2.4 → 2.3.1

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 +78 -207
  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 +51 -14
  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 +78 -100
  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
@@ -8,7 +8,6 @@ import { Select } from '~components/Select'
8
8
  import { Tag } from '~components/Tag'
9
9
  import { useMediaQueries } from '~components/utils/useMediaQueries'
10
10
  import { MainActions } from './subcomponents/MainActions'
11
- import { MobileActions } from './subcomponents/MobileActions'
12
11
  import { SecondaryActions } from './subcomponents/SecondaryActions'
13
12
  import {
14
13
  type NavigationTabs,
@@ -18,7 +17,7 @@ import {
18
17
  type TitleBlockProps,
19
18
  type TitleBlockVariant,
20
19
  } from './types'
21
- import { createTabletOverflowMenuItems, isReversed } from './utils'
20
+ import { isReversed } from './utils'
22
21
  import styles from './TitleBlock.module.scss'
23
22
 
24
23
  const renderTag = (surveyStatus: SurveyStatus): ReactNode => {
@@ -258,7 +257,6 @@ export const TitleBlock = ({
258
257
  sectionTitleDescriptionAutomationId = 'TitleBlock__SectionTitleDescription',
259
258
  breadcrumbAutomationId = 'TitleBlock__Breadcrumb',
260
259
  breadcrumbTextAutomationId = 'TitleBlock__BreadcrumbText',
261
- autoHideMobileActionsMenu = false,
262
260
  }: TitleBlockProps): JSX.Element => {
263
261
  const hasNavigationTabs = navigationTabs && navigationTabs.length > 0
264
262
  const collapseNavigationArea =
@@ -343,19 +341,13 @@ export const TitleBlock = ({
343
341
  </>
344
342
  </div>
345
343
  </div>
346
- {(primaryAction ??
347
- defaultAction ??
348
- secondaryActions ??
349
- secondaryOverflowMenuItems) && (
344
+ {(primaryAction ?? defaultAction ?? secondaryActions) && (
350
345
  <MainActions
351
346
  primaryAction={primaryAction}
352
347
  defaultAction={defaultAction}
348
+ secondaryActions={secondaryActions}
349
+ secondaryOverflowMenuItems={secondaryOverflowMenuItems}
353
350
  reversed={isReversed(variant)}
354
- overflowMenuItems={createTabletOverflowMenuItems(
355
- secondaryActions,
356
- secondaryOverflowMenuItems,
357
- )}
358
- showOverflowMenu={isSmallOrMediumViewport}
359
351
  />
360
352
  )}
361
353
  </div>
@@ -395,18 +387,6 @@ export const TitleBlock = ({
395
387
  </div>
396
388
  </div>
397
389
  </div>
398
- <MobileActions
399
- primaryAction={primaryAction}
400
- defaultAction={defaultAction}
401
- secondaryActions={secondaryActions}
402
- secondaryOverflowMenuItems={secondaryOverflowMenuItems}
403
- drawerHandleLabelIconPosition={
404
- primaryAction && 'iconPosition' in primaryAction
405
- ? primaryAction.iconPosition
406
- : undefined
407
- }
408
- autoHide={autoHideMobileActionsMenu}
409
- />
410
390
  </div>
411
391
  </>
412
392
  )
@@ -1,5 +1,6 @@
1
1
  import React from 'react'
2
2
  import { type Meta, type StoryObj } from '@storybook/react'
3
+ import { expect, waitFor, within } from '@storybook/test'
3
4
  import { Icon } from '~components/Icon'
4
5
  import { assetUrl } from '~components/utils/hostedAssets'
5
6
  import { StickerSheet } from '~storybook/components/StickerSheet'
@@ -26,6 +27,47 @@ const SECONDARY_ACTIONS = [
26
27
  },
27
28
  ]
28
29
 
30
+ const viewports = {
31
+ viewports: {
32
+ default: {
33
+ name: 'desktop (above or equal to 1366)',
34
+ styles: { width: '1366px', height: '800px' },
35
+ type: 'desktop',
36
+ },
37
+ desktopSm: {
38
+ name: 'desktop-sm (1024 - 1365)',
39
+ styles: { width: '1365px', height: '800px' },
40
+ type: 'desktop',
41
+ },
42
+ tablet: {
43
+ name: 'tablet (672 - 1023)',
44
+ styles: { width: '1023px', height: '800px' },
45
+ type: 'desktop',
46
+ },
47
+ mobileResponsive: {
48
+ name: 'mobile-responsive (512 - 671)',
49
+ styles: { width: '671px', height: '800px' },
50
+ type: 'desktop',
51
+ },
52
+ mobile: {
53
+ name: 'mobile (361 - 512)',
54
+ styles: { width: '511px', height: '800px' },
55
+ type: 'desktop',
56
+ },
57
+ mobileXs: {
58
+ name: 'mobile (under 360)',
59
+ styles: { width: '360px', height: '800px' },
60
+ type: 'desktop',
61
+ },
62
+ },
63
+ defaultViewport: 'default',
64
+ }
65
+
66
+ const chromaticViewports = {
67
+ disable: false,
68
+ viewports: [1366, 1365, 1079, 360, 320],
69
+ }
70
+
29
71
  const meta = {
30
72
  title: 'Components/TitleBlock',
31
73
  component: TitleBlock,
@@ -94,54 +136,41 @@ export const Playground: Story = {
94
136
 
95
137
  export const Viewports: Story = {
96
138
  parameters: {
97
- viewport: {
98
- viewports: {
99
- default: {
100
- name: 'Above or equal to 1366',
101
- styles: { width: '1366px', height: '800px' },
102
- type: 'desktop',
103
- },
104
- under1366: {
105
- name: 'Under 1366',
106
- styles: { width: '1365px', height: '800px' },
107
- type: 'desktop',
108
- },
109
- mediumSmall: {
110
- name: 'Medium and small',
111
- styles: { width: '1079px', height: '800px' },
112
- type: 'desktop',
113
- },
114
- },
115
- defaultViewport: 'default',
116
- },
117
- chromatic: {
118
- disable: false,
119
- viewports: [1079, 1365, 1366],
120
- },
139
+ viewport: viewports,
140
+ chromatic: chromaticViewports,
141
+ },
142
+ }
143
+ export const WithTabNavigation: Story = {
144
+ parameters: {
145
+ viewport: viewports,
146
+ chromatic: chromaticViewports,
147
+ },
148
+ play: async ({ canvasElement, step }) => {
149
+ const canvas = within(canvasElement.parentElement!)
150
+
151
+ const TabNavigation = await canvas.findByRole('navigation', {
152
+ name: 'Page title',
153
+ })
154
+
155
+ await step('Navigation Tab is present', async () => {
156
+ await waitFor(() => expect(TabNavigation).toBeInTheDocument())
157
+ })
158
+
159
+ await step('Navigation is focusable', async () => {
160
+ const NavigationList = canvas.getByRole('list')
161
+ const listItems = within(NavigationList).getAllByRole('listitem')
162
+
163
+ const thirdNavigationLink = within(listItems[2]).getByRole('link')
164
+ thirdNavigationLink.focus()
165
+ expect(thirdNavigationLink).toHaveFocus()
166
+ })
121
167
  },
122
168
  }
123
169
 
124
170
  export const HasLongTitle: Story = {
125
171
  parameters: {
126
- viewport: {
127
- viewports: {
128
- default: {
129
- name: 'Above or equal to 1366',
130
- styles: { width: '1366px', height: '800px' },
131
- type: 'desktop',
132
- },
133
- under1366: {
134
- name: 'Under 1366',
135
- styles: { width: '1365px', height: '800px' },
136
- type: 'desktop',
137
- },
138
- },
139
- defaultViewport: 'default',
140
- },
141
- chromatic: {
142
- disable: false,
143
- viewports: [1365, 1366],
144
- },
172
+ viewport: viewports,
173
+ chromatic: chromaticViewports,
145
174
  },
146
175
  args: { title: 'A long title with over thirty characters' },
147
176
  }
@@ -153,25 +182,8 @@ export const LightVariant: Story = {
153
182
  sourceState: 'shown',
154
183
  },
155
184
  },
156
- viewport: {
157
- viewports: {
158
- default: {
159
- name: 'Above or equal to 1366',
160
- styles: { width: '1366px', height: '800px' },
161
- type: 'desktop',
162
- },
163
- under1366: {
164
- name: 'Under 1366',
165
- styles: { width: '1365px', height: '800px' },
166
- type: 'desktop',
167
- },
168
- },
169
- defaultViewport: 'default',
170
- },
171
- chromatic: {
172
- disable: false,
173
- viewports: [1365, 1366],
174
- },
185
+ viewport: viewports,
186
+ chromatic: chromaticViewports,
175
187
  },
176
188
  args: {
177
189
  variant: 'light',
@@ -189,25 +201,8 @@ export const LightVariant: Story = {
189
201
 
190
202
  export const AdminVariant: Story = {
191
203
  parameters: {
192
- viewport: {
193
- viewports: {
194
- default: {
195
- name: 'Above or equal to 1366',
196
- styles: { width: '1366px', height: '800px' },
197
- type: 'desktop',
198
- },
199
- under1366: {
200
- name: 'Under 1366',
201
- styles: { width: '1365px', height: '800px' },
202
- type: 'desktop',
203
- },
204
- },
205
- defaultViewport: 'default',
206
- },
207
- chromatic: {
208
- disable: false,
209
- viewports: [1365, 1366],
210
- },
204
+ viewport: viewports,
205
+ chromatic: chromaticViewports,
211
206
  },
212
207
  args: {
213
208
  variant: 'admin',
@@ -224,25 +219,8 @@ export const AdminVariant: Story = {
224
219
 
225
220
  export const EducationVariant: Story = {
226
221
  parameters: {
227
- viewport: {
228
- viewports: {
229
- default: {
230
- name: 'Above or equal to 1366',
231
- styles: { width: '1366px', height: '800px' },
232
- type: 'desktop',
233
- },
234
- under1366: {
235
- name: 'Under 1366',
236
- styles: { width: '1365px', height: '800px' },
237
- type: 'desktop',
238
- },
239
- },
240
- defaultViewport: 'default',
241
- },
242
- chromatic: {
243
- disable: false,
244
- viewports: [1365, 1366],
245
- },
222
+ viewport: viewports,
223
+ chromatic: chromaticViewports,
246
224
  },
247
225
  args: {
248
226
  variant: 'education',
@@ -35,3 +35,9 @@
35
35
  @content;
36
36
  }
37
37
  }
38
+
39
+ @mixin title-block-xsmall {
40
+ @container (max-width: 511px) {
41
+ @content;
42
+ }
43
+ }
@@ -9,10 +9,35 @@
9
9
  flex-grow: 1;
10
10
  flex-shrink: 0;
11
11
 
12
+ .defaultActionButtonMinimized {
13
+ display: none;
14
+ margin-inline-end: 0;
15
+ }
16
+
12
17
  @include ca-margin($start: calc(#{$ca-grid} / 2));
13
18
 
14
- @include title-block-small {
15
- display: none;
19
+ @include title-block-xsmall {
20
+ padding-top: 12px;
21
+ justify-content: flex-start;
22
+ padding-left: 1.25rem;
23
+
24
+ @media (max-width: 511px) {
25
+ padding-top: 0;
26
+ padding-left: 3.375rem;
27
+ }
28
+
29
+ @media (max-width: 360px) {
30
+ .defaultActionButton {
31
+ display: none;
32
+ margin-inline-end: 0;
33
+ }
34
+
35
+ .defaultActionButtonMinimized {
36
+ display: flex;
37
+ margin-inline-end: 3px;
38
+ margin-left: -0.5rem;
39
+ }
40
+ }
16
41
  }
17
42
  }
18
43
  }
@@ -2,13 +2,14 @@ import React from 'react'
2
2
  import { Button, IconButton } from '~components/ButtonV1'
3
3
  import { Icon } from '~components/Icon'
4
4
  import { Menu, MenuList } from '~components/MenuV1'
5
- import { TITLE_BLOCK_ZEN_SECONDARY_MENU_HTML_ID } from '../constants'
5
+ import { TITLE_BLOCK_ZEN_SECONDARY_MENU_HTML_ID } from '~components/TitleBlock'
6
6
  import {
7
7
  type DefaultActionProps,
8
8
  type PrimaryActionProps,
9
+ type SecondaryActionsProps,
9
10
  type TitleBlockMenuItemProps,
10
11
  } from '../types'
11
- import { isMenuGroupNotButton } from '../utils'
12
+ import { createTabletOverflowMenuItems, isMenuGroupNotButton } from '../utils'
12
13
  import { TitleBlockMenuItem } from './TitleBlockMenuItem'
13
14
  import { Toolbar } from './Toolbar'
14
15
  import styles from './MainActions.module.scss'
@@ -17,18 +18,135 @@ type MainActionsProps = {
17
18
  primaryAction?: PrimaryActionProps
18
19
  defaultAction?: DefaultActionProps
19
20
  reversed?: boolean
20
- overflowMenuItems?: TitleBlockMenuItemProps[]
21
+ secondaryActions?: SecondaryActionsProps
22
+ secondaryOverflowMenuItems?: TitleBlockMenuItemProps[]
21
23
  showOverflowMenu?: boolean
22
24
  }
23
25
 
26
+ const renderSecondaryAndOverflowMenu = (
27
+ secondaryOverflowMenuItems?: TitleBlockMenuItemProps[],
28
+ reversed?: boolean,
29
+ ): JSX.Element | undefined => {
30
+ if (!secondaryOverflowMenuItems) return undefined
31
+ return (
32
+ <Menu
33
+ align="right"
34
+ button={
35
+ <IconButton
36
+ label={'Open secondary and overflow menu'}
37
+ reversed={reversed}
38
+ icon={<Icon name="more_horiz" isPresentational />}
39
+ id={TITLE_BLOCK_ZEN_SECONDARY_MENU_HTML_ID}
40
+ classNameOverride={styles.overflowMenuButton}
41
+ />
42
+ }
43
+ >
44
+ <MenuList>
45
+ {secondaryOverflowMenuItems.map((menuItem, i) => (
46
+ <TitleBlockMenuItem key={i} {...menuItem} />
47
+ ))}
48
+ </MenuList>
49
+ </Menu>
50
+ )
51
+ }
52
+
24
53
  export const MainActions = ({
25
54
  primaryAction,
26
55
  defaultAction,
56
+ secondaryActions,
57
+ secondaryOverflowMenuItems,
27
58
  reversed = false,
28
- overflowMenuItems,
29
- showOverflowMenu = false,
30
59
  }: MainActionsProps): JSX.Element => {
31
60
  let items
61
+
62
+ // Build combined secondary + overflow menu items once, to be spread into the toolbar items
63
+ const secondaryAndOverflowMenu: { key: string; node: JSX.Element }[] = []
64
+
65
+ // Convert defaultAction to menu item format and prepend to combined list
66
+ let defaultActionMenuItem: TitleBlockMenuItemProps | undefined
67
+ if (defaultAction) {
68
+ if ('component' in defaultAction) {
69
+ defaultActionMenuItem = defaultAction
70
+ } else if ('onClick' in defaultAction) {
71
+ defaultActionMenuItem = {
72
+ label: defaultAction.label,
73
+ icon: defaultAction.icon,
74
+ onClick: defaultAction.onClick,
75
+ disabled: defaultAction.disabled,
76
+ }
77
+ } else if ('href' in defaultAction) {
78
+ defaultActionMenuItem = {
79
+ label: defaultAction.label,
80
+ icon: defaultAction.icon,
81
+ href: defaultAction.href,
82
+ disabled: defaultAction.disabled,
83
+ }
84
+ }
85
+ }
86
+
87
+ const combinedSecondaryOverflowItems = createTabletOverflowMenuItems(
88
+ secondaryActions,
89
+ secondaryOverflowMenuItems,
90
+ )
91
+
92
+ // Prepend defaultAction to the combined list if it exists
93
+ const allMenuItems = defaultActionMenuItem
94
+ ? [defaultActionMenuItem, ...combinedSecondaryOverflowItems]
95
+ : combinedSecondaryOverflowItems
96
+
97
+ if (allMenuItems.length > 0) {
98
+ secondaryAndOverflowMenu.push({
99
+ key: 'overflowMenu',
100
+ node: renderSecondaryAndOverflowMenu(allMenuItems, reversed)!,
101
+ })
102
+ }
103
+
104
+ const defaultActionItem = defaultAction
105
+ ? [
106
+ {
107
+ key: 'defaultAction',
108
+ node: (
109
+ <Button
110
+ classNameOverride={styles.defaultActionButton}
111
+ {...{
112
+ ...defaultAction,
113
+ reversed: defaultAction.reversed ?? reversed,
114
+ }}
115
+ data-automation-id="title-block-default-action-button"
116
+ data-testid="title-block-default-action-button"
117
+ />
118
+ ),
119
+ },
120
+ ]
121
+ : []
122
+
123
+ const defaultActionItemMinimized = defaultAction
124
+ ? [
125
+ {
126
+ key: 'defaultActionMinimized',
127
+ node: (
128
+ <div className={styles.defaultActionButtonMinimized}>
129
+ <Menu
130
+ align="right"
131
+ button={
132
+ <IconButton
133
+ label={'Open default action overflow menu'}
134
+ reversed={reversed}
135
+ icon={<Icon name="more_horiz" isPresentational />}
136
+ id={TITLE_BLOCK_ZEN_SECONDARY_MENU_HTML_ID}
137
+ />
138
+ }
139
+ >
140
+ <MenuList>
141
+ {defaultActionMenuItem && <TitleBlockMenuItem {...defaultActionMenuItem} />}
142
+ </MenuList>
143
+ </Menu>
144
+ </div>
145
+ ),
146
+ },
147
+ ]
148
+ : []
149
+
32
150
  if (primaryAction && isMenuGroupNotButton(primaryAction)) {
33
151
  const menuContent = primaryAction.menuItems.map((item, idx) => (
34
152
  <TitleBlockMenuItem
@@ -40,23 +158,8 @@ export const MainActions = ({
40
158
  ))
41
159
 
42
160
  items = [
43
- ...(defaultAction
44
- ? [
45
- {
46
- key: 'defaultAction',
47
- node: (
48
- <Button
49
- {...{
50
- ...defaultAction,
51
- reversed: defaultAction.reversed ?? reversed,
52
- }}
53
- data-automation-id="title-block-default-action-button"
54
- data-testid="title-block-default-action-button"
55
- />
56
- ),
57
- },
58
- ]
59
- : []),
161
+ ...defaultActionItem,
162
+ ...defaultActionItemMinimized,
60
163
  ...(primaryAction
61
164
  ? [
62
165
  {
@@ -93,23 +196,8 @@ export const MainActions = ({
93
196
  ]
94
197
  } else {
95
198
  items = [
96
- ...(defaultAction
97
- ? [
98
- {
99
- key: 'defaultAction',
100
- node: (
101
- <Button
102
- {...{
103
- ...defaultAction,
104
- reversed: defaultAction.reversed ?? reversed,
105
- }}
106
- data-automation-id="title-block-default-action-button"
107
- data-testid="title-block-default-action-button"
108
- />
109
- ),
110
- },
111
- ]
112
- : []),
199
+ ...defaultActionItem,
200
+ ...defaultActionItemMinimized,
113
201
  ...(primaryAction
114
202
  ? [
115
203
  {
@@ -139,37 +227,6 @@ export const MainActions = ({
139
227
  ]
140
228
  }
141
229
 
142
- if (overflowMenuItems && showOverflowMenu && overflowMenuItems.length > 0) {
143
- items = [
144
- {
145
- key: 'overflowMenu',
146
- node: (
147
- <Menu
148
- align="right"
149
- button={
150
- <IconButton
151
- id={TITLE_BLOCK_ZEN_SECONDARY_MENU_HTML_ID}
152
- label="Open secondary menu"
153
- reversed={reversed}
154
- icon={<Icon name="more_horiz" isPresentational />}
155
- />
156
- }
157
- >
158
- <MenuList>
159
- {overflowMenuItems.map((menuItem, idx) => (
160
- <TitleBlockMenuItem
161
- {...menuItem}
162
- key={`main-action-overflow-item-menu-item-${idx}`}
163
- />
164
- ))}
165
- </MenuList>
166
- </Menu>
167
- ),
168
- },
169
- ...items,
170
- ]
171
- }
172
-
173
230
  return (
174
231
  <div className={styles.mainActionsContainer}>
175
232
  <Toolbar