willba-component-library 0.1.12 → 0.1.14

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 (66) hide show
  1. package/.nvmrc +1 -1
  2. package/.storybook/main.ts +19 -19
  3. package/.storybook/preview.ts +15 -15
  4. package/README.md +57 -57
  5. package/lib/components/FilterBar/components/buttons/select-button/SelectButton.d.ts +3 -0
  6. package/lib/index.esm.js +124 -117
  7. package/lib/index.esm.js.map +1 -1
  8. package/lib/index.js +124 -117
  9. package/lib/index.js.map +1 -1
  10. package/lib/index.umd.js +124 -117
  11. package/lib/index.umd.js.map +1 -1
  12. package/package.json +51 -51
  13. package/prettier.config.js +6 -6
  14. package/rollup.config.mjs +61 -61
  15. package/src/components/Button/Button.stories.tsx +34 -34
  16. package/src/components/Button/Button.tsx +56 -56
  17. package/src/components/Button/button.css +29 -29
  18. package/src/components/Button/index.ts +1 -1
  19. package/src/components/FilterBar/FilterBar.css +83 -83
  20. package/src/components/FilterBar/FilterBar.stories.tsx +47 -47
  21. package/src/components/FilterBar/FilterBar.tsx +180 -171
  22. package/src/components/FilterBar/FilterBarTypes.ts +25 -25
  23. package/src/components/FilterBar/components/{close-button → buttons/close-button}/CloseButton.css +33 -30
  24. package/src/components/FilterBar/components/{close-button → buttons/close-button}/CloseButton.tsx +16 -16
  25. package/src/components/FilterBar/components/{select-button → buttons/select-button}/SelectButton.css +36 -20
  26. package/src/components/FilterBar/components/buttons/select-button/SelectButton.tsx +24 -0
  27. package/src/components/FilterBar/components/{submit-button → buttons/submit-button}/SubmitButton.css +27 -27
  28. package/src/components/FilterBar/components/{submit-button → buttons/submit-button}/SubmitButton.tsx +18 -18
  29. package/src/components/FilterBar/components/calendar/Calendar.css +76 -76
  30. package/src/components/FilterBar/components/calendar/Calendar.tsx +52 -54
  31. package/src/components/FilterBar/components/categories/Categories.css +21 -21
  32. package/src/components/FilterBar/components/categories/Categories.tsx +41 -41
  33. package/src/components/FilterBar/components/divider/Divider.css +14 -14
  34. package/src/components/FilterBar/components/divider/Divider.tsx +7 -7
  35. package/src/components/FilterBar/components/guests/GuestCount/GuestCount.css +53 -53
  36. package/src/components/FilterBar/components/guests/GuestCount/GuestCount.tsx +51 -51
  37. package/src/components/FilterBar/components/guests/Guests.css +27 -27
  38. package/src/components/FilterBar/components/guests/Guests.tsx +38 -38
  39. package/src/components/FilterBar/hooks/useFilterBar.tsx +106 -106
  40. package/src/components/FilterBar/index.ts +1 -1
  41. package/src/i18n.ts +25 -25
  42. package/src/index.ts +4 -4
  43. package/src/locales/en/filterBar.json +20 -20
  44. package/src/locales/fi/filterBar.json +20 -20
  45. package/src/themes/Default.css +42 -42
  46. package/src/themes/useTheme.tsx +24 -24
  47. package/stories/Button.stories.ts +50 -50
  48. package/stories/Button.tsx +48 -48
  49. package/stories/Configure.mdx +364 -364
  50. package/stories/Header.stories.ts +27 -27
  51. package/stories/Header.tsx +56 -56
  52. package/stories/Page.stories.ts +29 -29
  53. package/stories/Page.tsx +73 -73
  54. package/stories/assets/accessibility.svg +4 -4
  55. package/stories/assets/discord.svg +15 -15
  56. package/stories/assets/github.svg +3 -3
  57. package/stories/assets/tutorials.svg +12 -12
  58. package/stories/assets/youtube.svg +4 -4
  59. package/stories/button.css +30 -30
  60. package/stories/header.css +32 -32
  61. package/stories/page.css +69 -69
  62. package/tsconfig.json +27 -27
  63. package/lib/components/FilterBar/components/select-button/SelectButton.d.ts +0 -3
  64. package/src/components/FilterBar/components/select-button/SelectButton.tsx +0 -15
  65. /package/lib/components/FilterBar/components/{close-button → buttons/close-button}/CloseButton.d.ts +0 -0
  66. /package/lib/components/FilterBar/components/{submit-button → buttons/submit-button}/SubmitButton.d.ts +0 -0
@@ -1,47 +1,47 @@
1
- import type { Meta, StoryObj } from '@storybook/react'
2
- import FilterBar from './FilterBar'
3
-
4
- // Default metadata of the story https://storybook.js.org/docs/react/api/csf#default-export
5
- const meta: Meta<typeof FilterBar> = {
6
- title: 'Components/FilterBar',
7
- component: FilterBar,
8
- }
9
-
10
- export default meta
11
-
12
- // The story type for the component https://storybook.js.org/docs/react/api/csf#named-story-exports
13
- type Story = StoryObj<typeof FilterBar>
14
-
15
- export const Primary: Story = {
16
- args: {
17
- language: '',
18
- redirectUrl: '',
19
- ageCategories: [
20
- {
21
- id: '1',
22
- name: 'Alle 6 vuotiaat',
23
- minAge: null,
24
- maxAge: 6,
25
- minVal: 1,
26
- },
27
- {
28
- id: '2',
29
- name: '6-16 vuotiaat',
30
- minAge: 6,
31
- maxAge: 17,
32
- minVal: 0,
33
- },
34
- {
35
- id: '3',
36
- name: 'Aikuiset',
37
- minAge: 17,
38
- maxAge: null,
39
- minVal: 0,
40
- },
41
- ],
42
- palette: {
43
- primary: '',
44
- secondary: '',
45
- },
46
- },
47
- }
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import FilterBar from './FilterBar'
3
+
4
+ // Default metadata of the story https://storybook.js.org/docs/react/api/csf#default-export
5
+ const meta: Meta<typeof FilterBar> = {
6
+ title: 'Components/FilterBar',
7
+ component: FilterBar,
8
+ }
9
+
10
+ export default meta
11
+
12
+ // The story type for the component https://storybook.js.org/docs/react/api/csf#named-story-exports
13
+ type Story = StoryObj<typeof FilterBar>
14
+
15
+ export const Primary: Story = {
16
+ args: {
17
+ language: '',
18
+ redirectUrl: '',
19
+ ageCategories: [
20
+ {
21
+ id: '1',
22
+ name: 'Alle 6 vuotiaat',
23
+ minAge: null,
24
+ maxAge: 6,
25
+ minVal: 1,
26
+ },
27
+ {
28
+ id: '2',
29
+ name: '6-16 vuotiaat',
30
+ minAge: 6,
31
+ maxAge: 17,
32
+ minVal: 0,
33
+ },
34
+ {
35
+ id: '3',
36
+ name: 'Aikuiset',
37
+ minAge: 17,
38
+ maxAge: null,
39
+ minVal: 0,
40
+ },
41
+ ],
42
+ palette: {
43
+ primary: '',
44
+ secondary: '',
45
+ },
46
+ },
47
+ }
@@ -1,171 +1,180 @@
1
- import React, { useEffect, useState, useRef } from 'react'
2
- import { useTranslation } from 'react-i18next'
3
-
4
- import Divider from './components/divider/Divider'
5
- import SelectButton from './components/select-button/SelectButton'
6
- import SubmitButton from './components/submit-button/SubmitButton'
7
- import Calendar from './components/calendar/Calendar'
8
- import Guests from './components/guests/Guests'
9
- import Categories from './components/categories/Categories'
10
-
11
- import useTheme, { Palette } from '../../themes/useTheme'
12
- import useFilterBar from './hooks/useFilterBar'
13
-
14
- import './FilterBar.css'
15
- import '../../themes/Default.css'
16
- import i18n from '../../i18n'
17
- import CloseButton from './components/close-button/CloseButton'
18
- import { AgeCategoryType } from './FilterBarTypes'
19
-
20
- export interface FilterBarProps {
21
- vendor?: string
22
- language?: string
23
- ageCategories?: AgeCategoryType[]
24
- redirectUrl: string
25
- palette?: Palette
26
- }
27
-
28
- export default function FilterBar({
29
- language,
30
- ageCategories = AGE_CATEGORIES_FALLBACK,
31
- redirectUrl = REDIRECT_URL_FALLBACK,
32
- palette,
33
- }: FilterBarProps) {
34
- const themePalette = useTheme({ palette })
35
-
36
- // Translations
37
- const [rerenderKey, setRerenderKey] = useState(0)
38
- useEffect(() => {
39
- i18n.changeLanguage(language)
40
-
41
- setRerenderKey((prevKey) => prevKey + 1)
42
- }, [language])
43
- const { t } = useTranslation('filterBar')
44
-
45
- // Filters
46
- const {
47
- selectedFilter,
48
- ageCategoryCounts,
49
- categories,
50
- calendarRange,
51
- setCalendarRange,
52
- setCategories,
53
- handleSelectedFilter,
54
- handleSubmit,
55
- updateGuestsCount,
56
- } = useFilterBar({ redirectUrl })
57
-
58
- // Scroll in to view
59
-
60
- const targetFilterBarRef = useRef<HTMLDivElement | null>(null)
61
- useEffect(() => {
62
- if (targetFilterBarRef.current && selectedFilter) {
63
- window.scrollTo({
64
- behavior: 'smooth',
65
- top:
66
- targetFilterBarRef.current.getBoundingClientRect().top -
67
- document.body.getBoundingClientRect().top -
68
- 30,
69
- })
70
- }
71
- }, [selectedFilter])
72
-
73
- return (
74
- <>
75
- {selectedFilter && (
76
- <div
77
- className={`will-filter-bar will-filter-bar-underlay`}
78
- onClick={() => {
79
- handleSelectedFilter(false)
80
- }}
81
- />
82
- )}
83
- <div
84
- className={`will-root ${selectedFilter ? 'isMobileAbsolute' : ''}`}
85
- style={themePalette}
86
- >
87
- <div className="will-filter-bar-header" ref={targetFilterBarRef}>
88
- <SelectButton
89
- style={fontWigthBold(selectedFilter === 1 || selectedFilter === 2)}
90
- label={t('calendar.startDate')}
91
- onClick={() => handleSelectedFilter(1)}
92
- />
93
- <Divider />
94
- <SelectButton
95
- style={fontWigthBold(selectedFilter === 1 || selectedFilter === 2)}
96
- label={t('calendar.endDate')}
97
- onClick={() => handleSelectedFilter(2)}
98
- />
99
-
100
- {/* TODO - Add strapi settings to show or hide filter sections */}
101
- {/* <Divider />
102
- <SelectButton
103
- label={t('guests.label')}
104
- onClick={() => handleSelectedFilter(3)}
105
- style={fontWigthBold(selectedFilter === 3)}
106
- /> */}
107
-
108
- {/* <Divider />
109
- <SelectButton
110
- label={t('categories.label')}
111
- onClick={() => handleSelectedFilter(4)}
112
- style={fontWigthBold(selectedFilter === 4)}
113
- /> */}
114
-
115
- <SubmitButton onClick={handleSubmit} />
116
- </div>
117
-
118
- {selectedFilter && (
119
- <div className="will-filter-bar-container">
120
- <CloseButton handleClose={() => handleSelectedFilter(false)} />
121
-
122
- {(selectedFilter === 1 || selectedFilter === 2) && (
123
- <Calendar
124
- calendarRange={calendarRange}
125
- setCalendarRange={setCalendarRange}
126
- />
127
- )}
128
- {selectedFilter === 3 && (
129
- <Guests
130
- updateGuestsCount={updateGuestsCount}
131
- ageCategories={ageCategories}
132
- ageCategoryCounts={ageCategoryCounts}
133
- />
134
- )}
135
- {selectedFilter === 4 && (
136
- <Categories
137
- categories={categories}
138
- setCategories={setCategories}
139
- />
140
- )}
141
- </div>
142
- )}
143
- </div>
144
- </>
145
- )
146
- }
147
-
148
- ////////////
149
-
150
- const fontWigthBold = (input: boolean) => {
151
- return { fontWeight: input ? 'bold' : 'initial' }
152
- }
153
-
154
- const AGE_CATEGORIES_FALLBACK = [
155
- {
156
- id: '1',
157
- name: 'Adults',
158
- minAge: null,
159
- maxAge: 6,
160
- minVal: 1,
161
- },
162
- {
163
- id: '2',
164
- name: 'Kids',
165
- minAge: 6,
166
- maxAge: 17,
167
- minVal: 0,
168
- },
169
- ]
170
-
171
- const REDIRECT_URL_FALLBACK = 'http://localhost:3000/'
1
+ import React, { useEffect, useState, useRef } from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import { format } from 'date-fns'
4
+
5
+ import Divider from './components/divider/Divider'
6
+ import SelectButton from './components/buttons/select-button/SelectButton'
7
+ import SubmitButton from './components/buttons/submit-button/SubmitButton'
8
+ import Calendar from './components/calendar/Calendar'
9
+ import Guests from './components/guests/Guests'
10
+ import Categories from './components/categories/Categories'
11
+
12
+ import useTheme, { Palette } from '../../themes/useTheme'
13
+ import useFilterBar from './hooks/useFilterBar'
14
+
15
+ import './FilterBar.css'
16
+ import '../../themes/Default.css'
17
+ import i18n from '../../i18n'
18
+ import CloseButton from './components/buttons/close-button/CloseButton'
19
+ import { AgeCategoryType } from './FilterBarTypes'
20
+
21
+ export interface FilterBarProps {
22
+ vendor?: string
23
+ language?: string
24
+ ageCategories?: AgeCategoryType[]
25
+ redirectUrl: string
26
+ palette?: Palette
27
+ }
28
+
29
+ export default function FilterBar({
30
+ language,
31
+ ageCategories = AGE_CATEGORIES_FALLBACK,
32
+ redirectUrl = REDIRECT_URL_FALLBACK,
33
+ palette,
34
+ }: FilterBarProps) {
35
+ const themePalette = useTheme({ palette })
36
+
37
+ // Translations
38
+ const [rerenderKey, setRerenderKey] = useState(0)
39
+ useEffect(() => {
40
+ i18n.changeLanguage(language)
41
+
42
+ setRerenderKey((prevKey) => prevKey + 1)
43
+ }, [language])
44
+ const { t } = useTranslation('filterBar')
45
+
46
+ // Filters
47
+ const {
48
+ selectedFilter,
49
+ ageCategoryCounts,
50
+ categories,
51
+ calendarRange,
52
+ setCalendarRange,
53
+ setCategories,
54
+ handleSelectedFilter,
55
+ handleSubmit,
56
+ updateGuestsCount,
57
+ } = useFilterBar({ redirectUrl })
58
+
59
+ // Scroll in to view
60
+
61
+ const targetFilterBarRef = useRef<HTMLDivElement | null>(null)
62
+ useEffect(() => {
63
+ if (targetFilterBarRef.current && selectedFilter) {
64
+ window.scrollTo({
65
+ behavior: 'smooth',
66
+ top:
67
+ targetFilterBarRef.current.getBoundingClientRect().top -
68
+ document.body.getBoundingClientRect().top -
69
+ 30,
70
+ })
71
+ }
72
+ }, [selectedFilter])
73
+
74
+ return (
75
+ <>
76
+ {selectedFilter && (
77
+ <div
78
+ className={`will-filter-bar will-filter-bar-underlay`}
79
+ onClick={() => {
80
+ handleSelectedFilter(false)
81
+ }}
82
+ />
83
+ )}
84
+ <div
85
+ className={`will-root ${selectedFilter ? 'isMobileAbsolute' : ''}`}
86
+ style={themePalette}
87
+ >
88
+ <div className="will-filter-bar-header" ref={targetFilterBarRef}>
89
+ <SelectButton
90
+ style={fontWigthBold(selectedFilter === 1 || selectedFilter === 2)}
91
+ label={t('calendar.startDate')}
92
+ onClick={() => handleSelectedFilter(1)}
93
+ date={
94
+ calendarRange?.from
95
+ ? format(calendarRange.from, 'dd.MM.yyyy')
96
+ : null
97
+ }
98
+ />
99
+ <Divider />
100
+ <SelectButton
101
+ style={fontWigthBold(selectedFilter === 1 || selectedFilter === 2)}
102
+ label={t('calendar.endDate')}
103
+ onClick={() => handleSelectedFilter(2)}
104
+ date={
105
+ calendarRange?.to ? format(calendarRange.to, 'dd.MM.yyyy') : null
106
+ }
107
+ />
108
+
109
+ {/* TODO - Add strapi settings to show or hide filter sections */}
110
+ {/* <Divider />
111
+ <SelectButton
112
+ label={t('guests.label')}
113
+ onClick={() => handleSelectedFilter(3)}
114
+ style={fontWigthBold(selectedFilter === 3)}
115
+ /> */}
116
+
117
+ {/* <Divider />
118
+ <SelectButton
119
+ label={t('categories.label')}
120
+ onClick={() => handleSelectedFilter(4)}
121
+ style={fontWigthBold(selectedFilter === 4)}
122
+ /> */}
123
+
124
+ <SubmitButton onClick={handleSubmit} />
125
+ </div>
126
+
127
+ {selectedFilter && (
128
+ <div className="will-filter-bar-container">
129
+ <CloseButton handleClose={() => handleSelectedFilter(false)} />
130
+
131
+ {(selectedFilter === 1 || selectedFilter === 2) && (
132
+ <Calendar
133
+ calendarRange={calendarRange}
134
+ setCalendarRange={setCalendarRange}
135
+ />
136
+ )}
137
+ {selectedFilter === 3 && (
138
+ <Guests
139
+ updateGuestsCount={updateGuestsCount}
140
+ ageCategories={ageCategories}
141
+ ageCategoryCounts={ageCategoryCounts}
142
+ />
143
+ )}
144
+ {selectedFilter === 4 && (
145
+ <Categories
146
+ categories={categories}
147
+ setCategories={setCategories}
148
+ />
149
+ )}
150
+ </div>
151
+ )}
152
+ </div>
153
+ </>
154
+ )
155
+ }
156
+
157
+ ////////////
158
+
159
+ const fontWigthBold = (input: boolean) => {
160
+ return { fontWeight: input ? 'bold' : 'initial' }
161
+ }
162
+
163
+ const AGE_CATEGORIES_FALLBACK = [
164
+ {
165
+ id: '1',
166
+ name: 'Adults',
167
+ minAge: null,
168
+ maxAge: 6,
169
+ minVal: 1,
170
+ },
171
+ {
172
+ id: '2',
173
+ name: 'Kids',
174
+ minAge: 6,
175
+ maxAge: 17,
176
+ minVal: 0,
177
+ },
178
+ ]
179
+
180
+ const REDIRECT_URL_FALLBACK = 'http://localhost:3000/'
@@ -1,25 +1,25 @@
1
- export interface AgeCategoryCount {
2
- [categoryId: string]: number
3
- }
4
-
5
- export interface AgeCategoryType {
6
- id: string
7
- name: string
8
- minAge: number | null
9
- maxAge: number | null
10
- minVal: number
11
- }
12
-
13
- export interface GuestsPropsType {
14
- ageCategories: AgeCategoryType[]
15
- updateGuestsCount: (arg1: number, arg2: number) => void
16
- ageCategoryCounts: AgeCategoryCount | {}
17
- }
18
-
19
- export interface GuestsCountPropsType {
20
- label: string
21
- id: number
22
- updateGuestsCount: (arg1: number, arg2: number) => void
23
- count: number
24
- minVal: number
25
- }
1
+ export interface AgeCategoryCount {
2
+ [categoryId: string]: number
3
+ }
4
+
5
+ export interface AgeCategoryType {
6
+ id: string
7
+ name: string
8
+ minAge: number | null
9
+ maxAge: number | null
10
+ minVal: number
11
+ }
12
+
13
+ export interface GuestsPropsType {
14
+ ageCategories: AgeCategoryType[]
15
+ updateGuestsCount: (arg1: number, arg2: number) => void
16
+ ageCategoryCounts: AgeCategoryCount | {}
17
+ }
18
+
19
+ export interface GuestsCountPropsType {
20
+ label: string
21
+ id: number
22
+ updateGuestsCount: (arg1: number, arg2: number) => void
23
+ count: number
24
+ minVal: number
25
+ }
@@ -1,30 +1,33 @@
1
- .will-filter-bar-close-button {
2
- width: auto;
3
- height: auto;
4
- /* background-color: var(--will-grey); */
5
- color: var(--will-grey);
6
- padding: 2px 7px;
7
- border-radius: 50%;
8
- cursor: pointer;
9
- border: none;
10
- display: flex;
11
- align-items: center;
12
- font-size: 23px;
13
- display: none;
14
-
15
- position: absolute;
16
- top: 10px;
17
- right: 10px;
18
-
19
- }
20
-
21
- @media (max-width: 960px) {
22
- .will-filter-bar-close-button {
23
- min-height: 35px;
24
- border-radius: 25px;
25
- margin-left:0;
26
-
27
- display: flex;
28
- justify-content: center;
29
- }
30
- }
1
+ .will-filter-bar-close-button {
2
+ width: auto;
3
+ height: auto;
4
+ /* background-color: var(--will-grey); */
5
+ color: var(--will-grey);
6
+ padding: 2px 7px;
7
+ border-radius: 50%;
8
+ cursor: pointer;
9
+ border: none;
10
+ display: flex;
11
+ align-items: center;
12
+ font-size: 23px;
13
+ /* display: none; */
14
+
15
+ position: absolute;
16
+ top: 80px;
17
+ right: 20px;
18
+
19
+ min-height: 35px;
20
+ }
21
+
22
+ @media (max-width: 960px) {
23
+ .will-filter-bar-close-button {
24
+ top: 10px;
25
+ right: 10px;
26
+
27
+ border-radius: 25px;
28
+ margin-left:0;
29
+
30
+ display: flex;
31
+ justify-content: center;
32
+ }
33
+ }
@@ -1,16 +1,16 @@
1
- import React from 'react'
2
- import { IoIosCloseCircleOutline } from 'react-icons/io'
3
-
4
- import './CloseButton.css'
5
-
6
- interface CloseButtonPropsType {
7
- handleClose: () => void
8
- }
9
-
10
- export default function CloseButton({ handleClose }: CloseButtonPropsType) {
11
- return (
12
- <button className="will-filter-bar-close-button" onClick={handleClose}>
13
- <IoIosCloseCircleOutline />
14
- </button>
15
- )
16
- }
1
+ import React from 'react'
2
+ import { IoIosCloseCircleOutline } from 'react-icons/io'
3
+
4
+ import './CloseButton.css'
5
+
6
+ interface CloseButtonPropsType {
7
+ handleClose: () => void
8
+ }
9
+
10
+ export default function CloseButton({ handleClose }: CloseButtonPropsType) {
11
+ return (
12
+ <button className="will-filter-bar-close-button" onClick={handleClose}>
13
+ <IoIosCloseCircleOutline />
14
+ </button>
15
+ )
16
+ }
@@ -1,20 +1,36 @@
1
- .will-filter-bar-select-button {
2
- width: 100%;
3
- height: auto;
4
- background-color: transparent;
5
- border: none;
6
- padding: 10px 20px;
7
- border-radius: 20px;
8
- cursor: pointer;
9
- font-size: 15px;
10
- text-align: initial;
11
- }
12
-
13
- @media (max-width: 960px) {
14
- .will-filter-bar-select-button {
15
- margin: 15px 0;
16
- text-align: center;
17
- }
18
- }
19
-
20
-
1
+ .will-filter-bar-select-button {
2
+ width: 100%;
3
+ height: auto;
4
+ background-color: transparent;
5
+ border: none;
6
+ padding: 10px 20px;
7
+ border-radius: 20px;
8
+ cursor: pointer;
9
+ font-size: 15px;
10
+ text-align: initial;
11
+ }
12
+
13
+ .will-filter-bar-select-button .select-button-wrapper {
14
+ display: flex;
15
+ align-items: center;
16
+ flex-wrap: wrap;
17
+ gap: 10px;
18
+
19
+ }
20
+
21
+ @media (max-width: 960px) {
22
+ .will-filter-bar-select-button {
23
+ margin: 15px 0;
24
+ }
25
+
26
+ .will-filter-bar-select-button .select-button-wrapper {
27
+ justify-content: center;
28
+ text-align: center;
29
+ }
30
+
31
+ .will-filter-bar-select-button .select-button-divider {
32
+ display: none
33
+ }
34
+ }
35
+
36
+