@cnamts/synapse 0.0.2-alpha → 0.0.3-alpha

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 (46) hide show
  1. package/README.md +1 -1
  2. package/dist/design-system-v3.js +957 -4826
  3. package/dist/design-system-v3.umd.cjs +1 -2
  4. package/dist/style.css +1 -1
  5. package/package.json +29 -29
  6. package/src/components/Alert/Alert.vue +8 -8
  7. package/src/components/FranceConnectBtn/FranceConnectBtn.vue +2 -2
  8. package/src/components/HeaderBar/HeaderBar.mdx +137 -0
  9. package/src/components/HeaderBar/HeaderBar.stories.ts +159 -0
  10. package/src/components/HeaderBar/HeaderBar.vue +238 -0
  11. package/src/components/HeaderBar/HeaderComplexMenu/HeaderComplexMenu.stories.ts +272 -0
  12. package/src/components/HeaderBar/HeaderComplexMenu/HeaderComplexMenu.vue +205 -0
  13. package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuItem/HeaderMenuItem.stories.ts +49 -0
  14. package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuItem/HeaderMenuItem.vue +51 -0
  15. package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuItem/tests/HeaderMenuItem.spec.ts +16 -0
  16. package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuItem/tests/__snapshots__/HeaderMenuItem.spec.ts.snap +3 -0
  17. package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuSection/HeaderMenuSection.stories.ts +56 -0
  18. package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuSection/HeaderMenuSection.vue +51 -0
  19. package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuSection/tests/HeaderMenuSection.spec.ts +33 -0
  20. package/src/components/HeaderBar/HeaderComplexMenu/HeaderSubMenu/HeaderSubMenu.stories.ts +137 -0
  21. package/src/components/HeaderBar/HeaderComplexMenu/HeaderSubMenu/HeaderSubMenu.vue +180 -0
  22. package/src/components/HeaderBar/HeaderComplexMenu/HeaderSubMenu/tests/HeaderSubMenu.spec.ts +63 -0
  23. package/src/components/HeaderBar/HeaderComplexMenu/conts.ts +1 -0
  24. package/src/components/HeaderBar/HeaderComplexMenu/locals.ts +4 -0
  25. package/src/components/HeaderBar/HeaderComplexMenu/tests/HeaderComplexMenu.spec.ts +129 -0
  26. package/src/components/HeaderBar/HeaderComplexMenu/tests/__snapshots__/HeaderComplexMenu.spec.ts.snap +18 -0
  27. package/src/components/HeaderBar/HeaderComplexMenu/tests/useHandleSubMenus.spec.ts +158 -0
  28. package/src/components/HeaderBar/HeaderComplexMenu/useHandleSubMenus.ts +49 -0
  29. package/src/components/HeaderBar/HeaderLogo/HeaderLogo.vue +106 -0
  30. package/src/components/HeaderBar/HeaderLogo/locales.ts +3 -0
  31. package/src/components/HeaderBar/HeaderLogo/logos/Logo-mobile.vue +117 -0
  32. package/src/components/HeaderBar/HeaderLogo/logos/Logo.vue +279 -0
  33. package/src/components/HeaderBar/HeaderLogo/tests/HeaderLogo.spec.ts +71 -0
  34. package/src/components/HeaderBar/HeaderMenuBtn/HeaderMenuBtn.vue +88 -0
  35. package/src/components/HeaderBar/HeaderMenuBtn/locals.ts +4 -0
  36. package/src/components/HeaderBar/consts.scss +7 -0
  37. package/src/components/HeaderBar/consts.ts +2 -0
  38. package/src/components/HeaderBar/locales.ts +3 -0
  39. package/src/components/HeaderBar/tests/HeaderBar.spec.ts +210 -0
  40. package/src/components/HeaderBar/tests/__snapshots__/HeaderBar.spec.ts.snap +50 -0
  41. package/src/components/HeaderBar/tests/useHeaderResponsiveMode.spec.ts +26 -0
  42. package/src/components/HeaderBar/tests/useScrollDirection.spec.ts +34 -0
  43. package/src/components/HeaderBar/useHeaderResponsiveMode.ts +25 -0
  44. package/src/components/HeaderBar/useScrollDirection.ts +26 -0
  45. package/src/components/NotificationBar/NotificationBar.vue +5 -7
  46. package/src/components/PageContainer/PageContainer.vue +0 -1
@@ -0,0 +1,205 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject, nextTick, onMounted, onUnmounted, readonly, ref, watch, type CSSProperties, type Ref } from 'vue'
3
+ import HeaderMenuBtn from '../HeaderMenuBtn/HeaderMenuBtn.vue'
4
+ import { registerHeaderMenuKey } from '../consts'
5
+ import locals from './locals'
6
+ import useHandleSubMenus from './useHandleSubMenus'
7
+
8
+ const menuWrapper = ref<HTMLElement | null>(null)
9
+ const menuBtnWrapper = ref<HTMLDivElement | null>(null)
10
+ const outerBtn = ref<HTMLElement | null>(null)
11
+ const innerBtn = ref<HTMLElement | null>(null)
12
+ const menuLeft = ref(0)
13
+ const menuTop = ref(0)
14
+
15
+ function positionMenu() {
16
+ // todo debounce
17
+ menuLeft.value = menuBtnWrapper.value!.getBoundingClientRect().left
18
+ menuTop.value = menuBtnWrapper.value!.getBoundingClientRect().top
19
+ }
20
+
21
+ onMounted(() => {
22
+ positionMenu()
23
+ window.addEventListener('scroll', positionMenu)
24
+ window.addEventListener('resize', positionMenu)
25
+ window.addEventListener('click', handleClickOutside, { capture: true })
26
+ })
27
+
28
+ onUnmounted(() => {
29
+ window.removeEventListener('scroll', positionMenu)
30
+ window.removeEventListener('resize', positionMenu)
31
+ window.removeEventListener('click', handleClickOutside, { capture: true })
32
+ })
33
+
34
+ const menuOpen = ref(false)
35
+
36
+ watch(menuOpen, async (newVal) => {
37
+ document.documentElement.style.overflow = newVal ? 'hidden' : 'auto'
38
+ document.body.style.overflow = newVal ? 'hidden' : 'auto'
39
+
40
+ if (newVal) {
41
+ await nextTick()
42
+ innerBtn.value!.focus()
43
+ }
44
+ else {
45
+ outerBtn.value!.focus()
46
+ }
47
+ })
48
+
49
+ const menuStyle = computed<CSSProperties>(() => ({
50
+ left: `${menuLeft.value}px`,
51
+ top: `${menuTop.value}px`,
52
+ }))
53
+
54
+ function handleClickOutside(event: MouseEvent | KeyboardEvent) {
55
+ if (!menuOpen.value) return
56
+
57
+ // do not close menu if click is inside the menu
58
+ let walkElement = event.target as HTMLElement | null
59
+ while (walkElement && walkElement !== document.body) {
60
+ if (walkElement === menuWrapper.value) return
61
+ walkElement = walkElement.parentElement
62
+ }
63
+
64
+ menuOpen.value = false
65
+ }
66
+
67
+ const { haveOpenSubMenu } = useHandleSubMenus(readonly(menuOpen))
68
+
69
+ const registerHeaderMenu = inject<(menuOpen: Ref<boolean>) => void>(registerHeaderMenuKey)
70
+ if (registerHeaderMenu) registerHeaderMenu(menuOpen)
71
+ </script>
72
+ <template>
73
+ <div
74
+ role="dialog"
75
+ aria-modal="true"
76
+ :aria-label="locals.mainMenu"
77
+ >
78
+ <div ref="menuBtnWrapper">
79
+ <HeaderMenuBtn
80
+ ref="outerBtn"
81
+ v-model="menuOpen"
82
+ />
83
+ </div>
84
+ <Transition name="menu">
85
+ <div
86
+ v-show="menuOpen"
87
+ class="overlay"
88
+ >
89
+ <div
90
+ ref="menuWrapper"
91
+ role="menu"
92
+ class="menu-wrapper"
93
+ :style="menuStyle"
94
+ >
95
+ <HeaderMenuBtn
96
+ ref="innerBtn"
97
+ v-model="menuOpen"
98
+ />
99
+ <nav
100
+ id="header-menu-wrapper"
101
+ class="header-menu-wrapper"
102
+ :class="{
103
+ 'header-menu-wrapper--submenu-open': haveOpenSubMenu,
104
+ }"
105
+ role="navigation"
106
+ :aria-label="locals.publicMenu"
107
+ >
108
+ <div class="header-menu">
109
+ <slot />
110
+ </div>
111
+ </nav>
112
+ </div>
113
+ </div>
114
+ </Transition>
115
+ </div>
116
+ </template>
117
+
118
+ <style lang="scss" scoped>
119
+ @use '@/assets/tokens.scss' as *;
120
+ @use '../consts' as *;
121
+
122
+ .overlay {
123
+ inset: 0;
124
+ position: fixed;
125
+ z-index: 1000;
126
+ background-color: rgba(3, 16, 37, .5);
127
+ }
128
+
129
+ .menu-wrapper {
130
+ height: 100dvh;
131
+ background-color: $neutral-white;
132
+ display: flex;
133
+ flex-direction: column;
134
+ }
135
+
136
+ .header-menu-wrapper {
137
+ height: calc(100% - $header-height);
138
+ display: grid;
139
+ position: relative;
140
+ overflow: auto;
141
+ }
142
+
143
+ .header-menu-wrapper--submenu-open {
144
+ overflow: clip;
145
+ }
146
+
147
+ @media screen and (min-width: $header-breakpoint) {
148
+ .menu-wrapper {
149
+ position: absolute;
150
+ background-color: transparent;
151
+ }
152
+
153
+ .header-menu-wrapper {
154
+ height: $menu-height;
155
+ width: $menu-width;
156
+ overflow: visible;
157
+ }
158
+
159
+ .header-menu {
160
+ background-color: $neutral-white;
161
+ overflow-y : auto;
162
+ overflow-x: hidden;
163
+ height: $menu-height;
164
+ }
165
+ }
166
+
167
+ .menu-enter-active {
168
+ transition: opacity 0.15s ease-in;
169
+
170
+ .header-menu-wrapper {
171
+ transition: transform 0.1s ease-in;
172
+ }
173
+ }
174
+
175
+ .menu-leave-active {
176
+ transition: opacity 0.15s ease-out;
177
+
178
+ .header-menu-wrapper {
179
+ transition: transform 0.1s ease-out;
180
+ }
181
+ }
182
+
183
+ .menu-enter-from, .menu-leave-to {
184
+ opacity: 0;
185
+ }
186
+
187
+ @media screen and (min-width: $header-breakpoint) {
188
+ .menu-enter-from, .menu-leave-to {
189
+ .header-menu-wrapper {
190
+ transform: translateY(10px);
191
+ }
192
+ }
193
+ }
194
+
195
+ @media (prefers-reduced-motion: reduce) {
196
+ .menu-enter-active, .menu-leave-active {
197
+ transition: opacity 0s;
198
+ }
199
+ .menu-enter-from, .menu-leave-to {
200
+ .header-menu-wrapper {
201
+ transform: none;
202
+ }
203
+ }
204
+ }
205
+ </style>
@@ -0,0 +1,49 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import HeaderMenuItem from './HeaderMenuItem.vue'
3
+ import HeaderMenu from '../HeaderComplexMenu.vue'
4
+ import HeaderBar from '../../HeaderBar.vue'
5
+
6
+ const meta = {
7
+ title: 'Components/HeaderBar/HeaderComplexMenu/HeaderMenuItem',
8
+ component: HeaderMenuItem,
9
+ parameters: {
10
+ layout: 'fullscreen',
11
+ },
12
+ } satisfies Meta<typeof HeaderMenuItem>
13
+
14
+ export default meta
15
+
16
+ type Story = StoryObj<typeof meta>
17
+
18
+ export const Default: Story = {
19
+ args: {
20
+ default: 'lorem ipsum',
21
+ },
22
+ render: (args) => {
23
+ return {
24
+ components: { HeaderMenuItem, HeaderMenu, HeaderBar },
25
+ setup() {
26
+ return { args }
27
+ },
28
+ template: `
29
+ <HeaderBar>
30
+ <template #menu>
31
+ <HeaderMenu>
32
+ <HeaderMenuSection>
33
+ <HeaderMenuItem>
34
+ <a>{{ args.default }}</a>
35
+ </HeaderMenuItem>
36
+ </HeaderMenuSection>
37
+ </HeaderMenu>
38
+ </template>
39
+ </HeaderBar>
40
+ `,
41
+ }
42
+ },
43
+ play: async ({ canvasElement }) => {
44
+ const menuBtn = canvasElement.querySelector('button')
45
+ setTimeout(() => {
46
+ menuBtn!.click()
47
+ }, 1000)
48
+ },
49
+ }
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <li class="header-menu-item">
7
+ <slot />
8
+ </li>
9
+ </template>
10
+
11
+ <style lang="scss" scoped>
12
+ @use "@/assets/tokens.scss" as *;
13
+
14
+ .header-menu-item {
15
+ color: $primary-base;
16
+ list-style-type: none;
17
+ margin: 0;
18
+ padding: 0;
19
+ min-height: 44px; // accessibility requirement
20
+ font-weight: 700;
21
+
22
+ > :deep(a) {
23
+ display: flex;
24
+ flex-direction: column;
25
+ padding: 16px 50px 16px 20px;
26
+ text-decoration: none;
27
+ color: currentColor;
28
+
29
+ &:hover {
30
+ text-decoration: underline;
31
+ }
32
+
33
+ &:visited {
34
+ color: currentColor;
35
+ }
36
+
37
+ &::first-letter {
38
+ text-transform: uppercase;
39
+ }
40
+ }
41
+ }
42
+
43
+ .header-menu-item:hover {
44
+ background-color: $primary-base;
45
+ color: $neutral-white;
46
+
47
+ > :deep(a > *) {
48
+ color: $neutral-white !important;
49
+ }
50
+ }
51
+ </style>
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import HeaderMenuItem from '../HeaderMenuItem.vue'
4
+
5
+ describe('HeaderMenuItem', () => {
6
+ it('should render the component', async () => {
7
+ const wrapper = mount(HeaderMenuItem, {
8
+ slots: {
9
+ default: '<a>Test</a>',
10
+ },
11
+ })
12
+
13
+ expect(wrapper.html()).toMatchSnapshot()
14
+ expect(wrapper.find('a').text()).toBe('Test')
15
+ })
16
+ })
@@ -0,0 +1,3 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`HeaderMenuItem > should render the component 1`] = `"<li data-v-8ba695ac="" class="header-menu-item"><a>Test</a></li>"`;
@@ -0,0 +1,56 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import HeaderMenuSection from './HeaderMenuSection.vue'
3
+ import HeaderMenuItem from '../HeaderMenuItem/HeaderMenuItem.vue'
4
+ import HeaderMenu from '../HeaderComplexMenu.vue'
5
+ import HeaderBar from '../../HeaderBar.vue'
6
+
7
+ const meta = {
8
+ title: 'Components/HeaderBar/HeaderComplexMenu/HeaderMenuSection',
9
+ component: HeaderMenuSection,
10
+ parameters: {
11
+ layout: 'fullscreen',
12
+ },
13
+ } satisfies Meta<typeof HeaderMenuSection>
14
+
15
+ export default meta
16
+
17
+ type Story = StoryObj<typeof meta>
18
+
19
+ export const Default: Story = {
20
+ args: {
21
+ title: 'section 1',
22
+ },
23
+ render: (args) => {
24
+ return {
25
+ components: { HeaderMenuItem, HeaderMenu, HeaderBar, HeaderMenuSection },
26
+ setup() {
27
+ return { args }
28
+ },
29
+ template: `
30
+ <HeaderBar>
31
+ <template #menu>
32
+ <HeaderMenu>
33
+ <HeaderMenuSection :title="args.title">
34
+ <HeaderMenuItem>
35
+ <a>lorem ipsum</a>
36
+ </HeaderMenuItem>
37
+ </HeaderMenuSection>
38
+
39
+ <HeaderMenuSection title="section 2">
40
+ <HeaderMenuItem>
41
+ <a>lorem ipsum</a>
42
+ </HeaderMenuItem>
43
+ </HeaderMenuSection>
44
+ </HeaderMenu>
45
+ </template>
46
+ </HeaderBar>
47
+ `,
48
+ }
49
+ },
50
+ play: async ({ canvasElement }) => {
51
+ const menuBtn = canvasElement.querySelector('button')
52
+ setTimeout(() => {
53
+ menuBtn!.click()
54
+ }, 1000)
55
+ },
56
+ }
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import { useId } from 'vue'
3
+
4
+ defineProps<{
5
+ title?: string
6
+ }>()
7
+
8
+ const id = useId()
9
+ const groupId = `${id}-group`
10
+ const titleId = `${id}-group-title`
11
+ </script>
12
+
13
+ <template>
14
+ <div class="header-menu-section">
15
+ <div
16
+ v-if="title"
17
+ :id="titleId"
18
+ class="header-menu-section-title"
19
+ >
20
+ {{ title }}
21
+ </div>
22
+ <ul
23
+ :id="groupId"
24
+ role="group"
25
+ class="header-menu-section-list"
26
+ >
27
+ <slot />
28
+ </ul>
29
+ </div>
30
+ </template>
31
+
32
+ <style lang="scss" scoped>
33
+ @use "@/assets/tokens.scss" as *;
34
+ @use "../../consts.scss" as menu;
35
+
36
+ .header-menu-section {
37
+ list-style-type: none;
38
+ padding: 0;
39
+ margin: 0;
40
+ }
41
+
42
+ .header-menu-section-title {
43
+ padding: 40px 16px 8px 20px;
44
+ border-bottom: 1px solid menu.$menu-border-color;
45
+ font-size: 1.1rem;
46
+ margin-bottom: 8px;
47
+ color: #212529;
48
+ text-transform: capitalize;
49
+ font-weight: 700;
50
+ }
51
+ </style>
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import HeaderMenuSection from '../HeaderMenuSection.vue'
4
+
5
+ describe('HeaderMenuSection', () => {
6
+ it('should render the component', async () => {
7
+ const wrapper = mount(HeaderMenuSection, {
8
+ props: {
9
+ title: 'Section title',
10
+ },
11
+ slots: {
12
+ default: [
13
+ '<li><a>Test 1</a></li>',
14
+ '<li><a>Test 2</a></li>',
15
+ ],
16
+ },
17
+ })
18
+
19
+ expect(wrapper.find('.header-menu-section-title').text()).toBe('Section title')
20
+ expect(wrapper.find('.header-menu-section-list').element.children.length).toBe(2)
21
+ })
22
+
23
+ it('should render the component with no title', async () => {
24
+ const wrapper = mount(HeaderMenuSection, {
25
+ slots: {
26
+ default: '<li><a>Test 1</a></li>',
27
+ },
28
+ })
29
+
30
+ expect(wrapper.find('.header-menu-section-title').exists()).toBe(false)
31
+ expect(wrapper.find('.header-menu-section-list').element.children.length).toBe(1)
32
+ })
33
+ })
@@ -0,0 +1,137 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import HeaderBar from '../../HeaderBar.vue'
3
+ import HeaderMenu from '../HeaderComplexMenu.vue'
4
+ import HeaderMenuItem from '../HeaderMenuItem/HeaderMenuItem.vue'
5
+ import HeaderMenuSection from '../HeaderMenuSection/HeaderMenuSection.vue'
6
+ import HeaderSubMenu from './HeaderSubMenu.vue'
7
+
8
+ const meta = {
9
+ title: 'Components/HeaderBar/HeaderComplexMenu/HeaderSubMenu',
10
+ component: HeaderSubMenu,
11
+ parameters: {
12
+ layout: 'fullscreen',
13
+ },
14
+ } satisfies Meta<typeof HeaderSubMenu>
15
+
16
+ export default meta
17
+
18
+ type Story = StoryObj<typeof meta>
19
+
20
+ export const Default: Story = {
21
+ args: {
22
+ title: 'Menu de premier niveau',
23
+ },
24
+ render: (args) => {
25
+ return {
26
+ components: { HeaderMenuItem, HeaderMenu, HeaderBar, HeaderSubMenu, HeaderMenuSection },
27
+ setup() {
28
+ return { args }
29
+ },
30
+ template: `
31
+ <HeaderBar>
32
+ <template #menu>
33
+ <HeaderMenu>
34
+ <HeaderMenuSection>
35
+ <HeaderMenuItem>
36
+ <a>Item</a>
37
+ </HeaderMenuItem>
38
+ <HeaderMenuItem>
39
+ <a>Item</a>
40
+ </HeaderMenuItem>
41
+ <headerMenuItem>
42
+ <HeaderSubMenu>
43
+ <template #title>
44
+ {{ args.title }}
45
+ </template>
46
+ <HeaderMenuItem>
47
+ <a>Item</a>
48
+ </HeaderMenuItem>
49
+ </HeaderSubMenu>
50
+ </headerMenuItem>
51
+ <HeaderMenuItem>
52
+ <a>Item</a>
53
+ </HeaderMenuItem>
54
+ </HeaderMenuSection>
55
+ </HeaderMenu>
56
+ </template>
57
+ </HeaderBar>
58
+ `,
59
+ }
60
+ },
61
+ play: async ({ canvasElement }) => {
62
+ const menuBtn = canvasElement.querySelector('button')
63
+ setTimeout(() => {
64
+ menuBtn!.click()
65
+ }, 1000)
66
+ },
67
+ }
68
+
69
+ export const Deep: Story = {
70
+ args: {
71
+ title: 'Menu de deuxième niveau',
72
+ },
73
+ render: (args) => {
74
+ return {
75
+ components: { HeaderMenuItem, HeaderMenu, HeaderBar, HeaderSubMenu, HeaderMenuSection },
76
+ setup() {
77
+ return { args }
78
+ },
79
+ template: `
80
+ <HeaderBar>
81
+ <template #menu>
82
+ <HeaderMenu>
83
+ <HeaderMenuSection>
84
+ <HeaderMenuItem>
85
+ <a>Item 1</a>
86
+ </HeaderMenuItem>
87
+ <HeaderMenuItem>
88
+ <a>Item 2</a>
89
+ </HeaderMenuItem>
90
+ <headerMenuItem>
91
+ <HeaderSubMenu>
92
+ <template #title>
93
+ Menu de premier niveau
94
+ </template>
95
+ <HeaderMenuItem>
96
+ <a>Item 2.1</a>
97
+ </HeaderMenuItem>
98
+ <HeaderMenuItem>
99
+ <a>Item 2.2</a>
100
+ </HeaderMenuItem>
101
+ <HeaderMenuItem>
102
+ <HeaderSubMenu>
103
+ <template #title>
104
+ Menu de deuxième niveau
105
+ </template>
106
+ <HeaderMenuItem>
107
+ <a>Item 3.1</a>
108
+ </HeaderMenuItem>
109
+ <HeaderMenuItem>
110
+ <a>Item 3.2</a>
111
+ </HeaderMenuItem>
112
+ </HeaderSubMenu>
113
+ </HeaderMenuItem>
114
+ </HeaderSubMenu>
115
+ </headerMenuItem>
116
+ <HeaderMenuItem>
117
+ <a>Item 3</a>
118
+ </HeaderMenuItem>
119
+ </HeaderMenuSection>
120
+ </HeaderMenu>
121
+ </template>
122
+ </HeaderBar>
123
+ `,
124
+ }
125
+ },
126
+ play: async ({ canvasElement }) => {
127
+ const menuBtn = canvasElement.querySelector('button')
128
+ await new Promise(resolve => setTimeout(resolve, 1000))
129
+ await menuBtn!.click()
130
+ await new Promise(resolve => setTimeout(resolve, 500))
131
+ const subMenuBtn = canvasElement.querySelector<HTMLElement>('.sub-menu-btn')
132
+ await subMenuBtn!.click()
133
+ await new Promise(resolve => setTimeout(resolve, 500))
134
+ const subMenuBtn2 = canvasElement.querySelector<HTMLElement>('.sub-menu .sub-menu .sub-menu-btn')
135
+ await subMenuBtn2!.click()
136
+ },
137
+ }