ionbase-ui 0.81.0 → 0.84.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 (42) hide show
  1. package/dist/components/DatePicker.d.ts +1 -1
  2. package/dist/components/DatePicker.d.ts.map +1 -1
  3. package/dist/components/DatePicker.js +14 -6
  4. package/dist/components/DatePicker.js.map +1 -1
  5. package/dist/components/DateRangePicker.d.ts +1 -1
  6. package/dist/components/DateRangePicker.d.ts.map +1 -1
  7. package/dist/components/DateRangePicker.js +14 -6
  8. package/dist/components/DateRangePicker.js.map +1 -1
  9. package/dist/components/Menu.d.ts +87 -24
  10. package/dist/components/Menu.d.ts.map +1 -1
  11. package/dist/components/Menu.js +245 -28
  12. package/dist/components/Menu.js.map +1 -1
  13. package/dist/components/PageHeader.d.ts +59 -0
  14. package/dist/components/PageHeader.d.ts.map +1 -0
  15. package/dist/components/PageHeader.js +29 -0
  16. package/dist/components/PageHeader.js.map +1 -0
  17. package/dist/components/TimeField.d.ts +1 -1
  18. package/dist/components/TimeField.d.ts.map +1 -1
  19. package/dist/components/index.d.ts +4 -2
  20. package/dist/components/index.d.ts.map +1 -1
  21. package/dist/components/index.js +2 -1
  22. package/dist/components/index.js.map +1 -1
  23. package/dist/figma-descriptions.json +86 -76
  24. package/dist/figma-map.json +142 -38
  25. package/dist/meta/DatePicker.json +3 -2
  26. package/dist/meta/DateRangePicker.json +3 -2
  27. package/dist/meta/Menu.json +201 -27
  28. package/dist/meta/MenuItem.json +137 -17
  29. package/dist/meta/MenuSection.json +90 -0
  30. package/dist/meta/MenuTrigger.json +177 -0
  31. package/dist/meta/PageHeader.json +169 -0
  32. package/dist/meta/TimeField.json +1 -1
  33. package/dist/meta/components.json +841 -109
  34. package/dist/meta/contrast.json +240 -32
  35. package/dist/meta/index.json +31 -3
  36. package/dist/meta/patterns/PageShell.json +18 -5
  37. package/dist/meta/patterns/index.json +2 -1
  38. package/dist/styles/index.css +1 -0
  39. package/dist/styles/menu.css +153 -32
  40. package/dist/styles/page-header.css +94 -0
  41. package/llms.txt +2 -2
  42. package/package.json +1 -1
@@ -2,11 +2,12 @@
2
2
  "name": "MenuItem",
3
3
  "source": "src/components/Menu.tsx",
4
4
  "propsType": "MenuItemProps",
5
+ "description": "One row of a Menu. A collection element, like TabItem: it renders nothing\nitself — Menu reads its props and draws the row — so it only means anything\nas a direct child of Menu or MenuSection. Give it a `key`; that key is what\n`onAction` and `selectedKeys` speak in.",
5
6
  "import": "import { MenuItem } from 'ionbase-ui';",
6
7
  "status": "stable",
7
- "summary": "One row in a Menu. A real <button> inside an <li>, with an optional leading icon and a trailing selected check.",
8
+ "summary": "One row of a Menu. A collection element: Menu reads its props and draws the row as `menuitem`, `menuitemradio` or `menuitemcheckbox`.",
8
9
  "useWhen": [
9
- "a row in a Menu surface"
10
+ "a row in a Menu or a MenuSection — it is not usable anywhere else"
10
11
  ],
11
12
  "useInstead": [
12
13
  {
@@ -18,6 +19,14 @@
18
19
  "use": "Button"
19
20
  }
20
21
  ],
22
+ "composition": {
23
+ "order": [
24
+ "Menu",
25
+ "MenuItem"
26
+ ],
27
+ "note": "A collection element, like TabItem: it renders nothing itself and never appears in the DOM. `key` identifies it to Menu's `onAction`, `selectedKeys` and `disabledKeys`.",
28
+ "example": "<MenuItem key=\"duplicate\" icon={<Icon as={Copy} />}>Duplicate</MenuItem>\n<MenuItem key=\"move\" title=\"Move to\"><MenuItem key=\"archive\">Archive</MenuItem></MenuItem>"
29
+ },
21
30
  "slots": {
22
31
  "icon": {
23
32
  "accepts": "Icon",
@@ -26,37 +35,50 @@
26
35
  "children": {
27
36
  "accepts": "text",
28
37
  "note": "the row label"
38
+ },
39
+ "title": {
40
+ "accepts": "text",
41
+ "note": "only on a row that opens a submenu: the row's label, while its children are the submenu's MenuItems. Submenus open only inside a MenuTrigger"
29
42
  }
30
43
  },
31
44
  "a11y": {
32
- "role": "button",
33
45
  "guarantees": [
34
- "`isSelected` sets `aria-pressed`, so the selected state is announced and not only drawn",
35
- "the check glyph always occupies its slot, so rows never reflow as the selection moves"
46
+ "the role, `aria-checked` and `aria-disabled` are supplied by Menu from its selection mode and keys, not by this element",
47
+ "the check glyph occupies its slot whenever the menu has a selection mode, so rows never reflow as the selection moves"
36
48
  ],
37
49
  "requires": [
38
- "a text label in `children`"
50
+ "a stable `key`",
51
+ "text in `children`, or `textValue` when the children are not plain text — typeahead matches against it"
39
52
  ]
40
53
  },
41
54
  "antiPatterns": [
42
55
  {
43
56
  "dont": "using MenuItem outside a Menu",
44
- "why": "it renders an <li>, which is only valid inside a list"
57
+ "why": "it renders nothing on its own"
45
58
  },
46
59
  {
47
60
  "dont": "an icon-only MenuItem with no children",
48
- "why": "the row announces nothing"
61
+ "why": "the row announces nothing and typeahead cannot reach it"
62
+ },
63
+ {
64
+ "dont": "array indexes as keys in a list that can change",
65
+ "why": "selection and actions are expressed in keys, so the selected row silently becomes a different row"
49
66
  }
50
67
  ],
51
68
  "deprecated": [
52
69
  {
53
70
  "prop": "disabled",
54
71
  "replacement": "isDisabled"
72
+ },
73
+ {
74
+ "prop": "isSelected",
75
+ "note": "Put the item's key in Menu's `selectedKeys` and set its `selectionMode`."
55
76
  }
56
77
  ],
57
78
  "stylesheet": "src/styles/menu.css",
58
79
  "tokens": [
59
80
  "--border-focus",
81
+ "--border-strong",
60
82
  "--border-subtle",
61
83
  "--border-width-default",
62
84
  "--border-width-thick",
@@ -68,10 +90,13 @@
68
90
  "--icon-size-sm",
69
91
  "--ion-duration-base",
70
92
  "--ion-ease-out",
93
+ "--ion-menu-min-width",
94
+ "--ion-shadow-shadow-lg",
71
95
  "--radius-sm",
72
96
  "--radius-xl",
73
97
  "--spacing-12",
74
98
  "--spacing-2",
99
+ "--spacing-4",
75
100
  "--spacing-40",
76
101
  "--spacing-6",
77
102
  "--spacing-8",
@@ -81,15 +106,18 @@
81
106
  "--text-disabled",
82
107
  "--text-primary",
83
108
  "--text-secondary",
109
+ "--text-tertiary",
84
110
  "--type-body",
85
- "--type-body-line-height"
111
+ "--type-body-line-height",
112
+ "--type-caption",
113
+ "--type-caption-line-height"
86
114
  ],
87
115
  "props": {
88
- "isSelected": {
89
- "type": "boolean | undefined",
116
+ "title": {
117
+ "type": "React.ReactNode",
90
118
  "required": false,
91
119
  "origin": "own",
92
- "description": "Figma's `Selected` / `Selected-hover` states. Shows the trailing check."
120
+ "description": "Only on a row that opens a submenu: the row's label. Its children are then\nthe submenu's MenuItems rather than its label. Submenus open only inside a\nMenuTrigger — a standalone Menu has nothing to open them from."
93
121
  },
94
122
  "icon": {
95
123
  "type": "React.ReactNode",
@@ -101,7 +129,7 @@
101
129
  "type": "boolean | undefined",
102
130
  "required": false,
103
131
  "origin": "own",
104
- "description": "Whether the item is disabled."
132
+ "description": "Whether the item is disabled. Listed, skipped by the arrow keys."
105
133
  },
106
134
  "disabled": {
107
135
  "type": "boolean | undefined",
@@ -111,16 +139,108 @@
111
139
  "deprecated": "Use `isDisabled`. Accepted as an alias for one minor version."
112
140
  }
113
141
  },
142
+ "isSelected": {
143
+ "type": "boolean | undefined",
144
+ "required": false,
145
+ "origin": "own",
146
+ "tags": {
147
+ "deprecated": "Put the item's key in Menu's `selectedKeys`. Accepted for one\nminor version: when no item's key is in `selectedKeys` and Menu is given\nneither `selectedKeys` nor `defaultSelectedKeys`, the items passing this\nbecome the selection."
148
+ }
149
+ },
114
150
  "children": {
115
- "type": "React.ReactNode",
151
+ "type": "ReactNode",
152
+ "required": true,
153
+ "origin": "aria",
154
+ "description": "Rendered contents of the item or child items."
155
+ },
156
+ "textValue": {
157
+ "type": "string | undefined",
158
+ "required": false,
159
+ "origin": "aria",
160
+ "description": "A string representation of the item's contents, used for features like typeahead."
161
+ },
162
+ "aria-label": {
163
+ "type": "string | undefined",
164
+ "required": false,
165
+ "origin": "aria",
166
+ "description": "An accessibility label for this item."
167
+ },
168
+ "childItems": {
169
+ "type": "Iterable<T> | undefined",
170
+ "required": false,
171
+ "origin": "aria",
172
+ "description": "A list of child item objects. Used for dynamic collections."
173
+ },
174
+ "hasChildItems": {
175
+ "type": "boolean | undefined",
176
+ "required": false,
177
+ "origin": "aria",
178
+ "description": "Whether this item has children, even if not loaded yet."
179
+ },
180
+ "href": {
181
+ "type": "string | undefined",
182
+ "required": false,
183
+ "origin": "aria",
184
+ "description": "A URL to link to. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href)."
185
+ },
186
+ "hrefLang": {
187
+ "type": "string | undefined",
188
+ "required": false,
189
+ "origin": "aria",
190
+ "description": "Hints at the human language of the linked URL.\nSee[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#hreflang)."
191
+ },
192
+ "target": {
193
+ "type": "HTMLAttributeAnchorTarget | undefined",
194
+ "required": false,
195
+ "origin": "aria",
196
+ "description": "The target window for the link. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)."
197
+ },
198
+ "rel": {
199
+ "type": "string | undefined",
200
+ "required": false,
201
+ "origin": "aria",
202
+ "description": "The relationship between the linked resource and the current page. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel)."
203
+ },
204
+ "download": {
205
+ "type": "string | boolean | undefined",
206
+ "required": false,
207
+ "origin": "aria",
208
+ "description": "Causes the browser to download the linked URL. A string may be provided to suggest a file name.\nSee [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#download)."
209
+ },
210
+ "ping": {
211
+ "type": "string | undefined",
212
+ "required": false,
213
+ "origin": "aria",
214
+ "description": "A space-separated list of URLs to ping when the link is followed. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#ping)."
215
+ },
216
+ "referrerPolicy": {
217
+ "type": "HTMLAttributeReferrerPolicy | undefined",
218
+ "required": false,
219
+ "origin": "aria",
220
+ "description": "How much of the referrer to send when following the link. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#referrerpolicy).",
221
+ "values": [
222
+ "",
223
+ "origin",
224
+ "no-referrer",
225
+ "no-referrer-when-downgrade",
226
+ "origin-when-cross-origin",
227
+ "same-origin",
228
+ "strict-origin",
229
+ "strict-origin-when-cross-origin",
230
+ "unsafe-url"
231
+ ]
232
+ },
233
+ "routerOptions": {
234
+ "type": "undefined",
116
235
  "required": false,
117
- "origin": "own"
236
+ "origin": "aria",
237
+ "description": "Options for the configured client side router."
118
238
  }
119
239
  },
120
240
  "propCounts": {
121
241
  "own": 5,
122
- "aria": 0,
123
- "dom": 285,
242
+ "aria": 13,
243
+ "dom": 0,
124
244
  "other": 0
125
245
  }
126
246
  }
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "MenuSection",
3
+ "source": "../../node_modules/.pnpm/react-stately@3.48.0_react@19.2.8/node_modules/react-stately/dist/types/src/collections/Section.d.ts",
4
+ "propsType": null,
5
+ "import": "import { MenuSection } from 'ionbase-ui';",
6
+ "status": "stable",
7
+ "summary": "A named group of MenuItems. Re-exported from react-stately's collection Section — `title` is Figma's Menu Section Title.",
8
+ "useWhen": [
9
+ "a Menu has more than one kind of action, such as editing actions and destructive ones",
10
+ "the rows fall into groups the user scans by name"
11
+ ],
12
+ "useInstead": [
13
+ {
14
+ "when": "the menu has one group",
15
+ "use": "MenuItem",
16
+ "why": "a single section adds a heading the user must hear before every menu, and says nothing the menu's own label does not"
17
+ }
18
+ ],
19
+ "composition": {
20
+ "order": [
21
+ "Menu",
22
+ "MenuSection",
23
+ "MenuItem"
24
+ ],
25
+ "note": "A collection element: Menu reads it from its direct children. With `title`, the heading is drawn with a rule after it; without one, `aria-label` names the group and a rule is drawn above it instead.",
26
+ "example": "<MenuSection title=\"Sort by\"><MenuItem key=\"name\">Name</MenuItem><MenuItem key=\"updated\">Last updated</MenuItem></MenuSection>"
27
+ },
28
+ "a11y": {
29
+ "role": "group",
30
+ "guarantees": [
31
+ "the group is named by its title, so a screen reader announces it on entering the first row"
32
+ ],
33
+ "requires": [
34
+ "a `title`; an untitled section still needs a label for the group, or it is announced as an unnamed group"
35
+ ],
36
+ "notes": [
37
+ "The accessible-name lint rule is deliberately not driven from here: a titled section is named by its title and needs nothing more, so demanding a label on every MenuSection would be wrong for the common case."
38
+ ]
39
+ },
40
+ "antiPatterns": [
41
+ {
42
+ "dont": "nesting a MenuSection inside another",
43
+ "why": "a menu has one level of grouping; a section inside a section renders nothing"
44
+ }
45
+ ],
46
+ "stylesheet": "src/styles/menu.css",
47
+ "tokens": [
48
+ "--border-focus",
49
+ "--border-strong",
50
+ "--border-subtle",
51
+ "--border-width-default",
52
+ "--border-width-thick",
53
+ "--font-family-sans",
54
+ "--font-weight-regular",
55
+ "--icon-default",
56
+ "--icon-disabled",
57
+ "--icon-primary",
58
+ "--icon-size-sm",
59
+ "--ion-duration-base",
60
+ "--ion-ease-out",
61
+ "--ion-menu-min-width",
62
+ "--ion-shadow-shadow-lg",
63
+ "--radius-sm",
64
+ "--radius-xl",
65
+ "--spacing-12",
66
+ "--spacing-2",
67
+ "--spacing-4",
68
+ "--spacing-40",
69
+ "--spacing-6",
70
+ "--spacing-8",
71
+ "--surface-default",
72
+ "--surface-hover",
73
+ "--surface-primary-subtle",
74
+ "--text-disabled",
75
+ "--text-primary",
76
+ "--text-secondary",
77
+ "--text-tertiary",
78
+ "--type-body",
79
+ "--type-body-line-height",
80
+ "--type-caption",
81
+ "--type-caption-line-height"
82
+ ],
83
+ "props": {},
84
+ "propCounts": {
85
+ "own": 0,
86
+ "aria": 0,
87
+ "dom": 0,
88
+ "other": 0
89
+ }
90
+ }
@@ -0,0 +1,177 @@
1
+ {
2
+ "name": "MenuTrigger",
3
+ "source": "src/components/Menu.tsx",
4
+ "propsType": "MenuTriggerProps",
5
+ "description": "MenuTrigger — a Button that opens a Menu.\n\nThe \"⋯\" overflow menu is this with an icon-only Button; there is no separate\ncomponent for it, because the only difference is the Button's content and\nits required aria-label.\n\n`useMenuTrigger` does what a Popover wrapped round a Menu could not: the\ntrigger announces `aria-haspopup=\"menu\"` and `aria-expanded`, the menu is\nnamed by the trigger, ArrowDown and ArrowUp open it with focus on the first\nor last row, and choosing an action closes it and returns focus to the\ntrigger.",
6
+ "import": "import { MenuTrigger } from 'ionbase-ui';",
7
+ "status": "stable",
8
+ "summary": "A Button that opens a Menu. The \"⋯\" overflow menu is this with an icon-only Button. Owns open state, positioning, focus, and the ARIA link between the two.",
9
+ "useWhen": [
10
+ "a button opens a list of actions — row actions, an overflow \"⋯\", a \"New\" button with several kinds of new",
11
+ "a button opens a list of choices applied immediately, such as a sort order",
12
+ "the actions nest one level or more — submenus open only inside a MenuTrigger"
13
+ ],
14
+ "useInstead": [
15
+ {
16
+ "when": "the button opens a form, a filter panel or anything that is not a list of rows",
17
+ "use": "Popover",
18
+ "why": "a menu promises arrow keys between rows and nothing else; a field inside it cannot be reached"
19
+ },
20
+ {
21
+ "when": "the choice is a form field's value",
22
+ "use": "Select",
23
+ "why": "a form value is a listbox the form can read, not a menu of actions"
24
+ },
25
+ {
26
+ "when": "there are only two or three actions and room to show them",
27
+ "use": "Button",
28
+ "why": "hiding actions behind a menu costs a click and makes them undiscoverable; show them when they fit"
29
+ }
30
+ ],
31
+ "composition": {
32
+ "order": [
33
+ "MenuTrigger",
34
+ "Button",
35
+ "Menu"
36
+ ],
37
+ "note": "Exactly two children, in this order: the Button, then the Menu. The Menu is named by the Button unless it has its own aria-label. A MenuItem with a `title` and MenuItem children opens a submenu; `onAction` on the root Menu receives actions from every level.",
38
+ "example": "<MenuTrigger><Button variant=\"tertiary\" aria-label=\"Actions for Payroll\" startIcon={<Icon as={Ellipsis} />} /><Menu onAction={(key) => run(key)}><MenuItem key=\"rename\">Rename</MenuItem><MenuItem key=\"move\" title=\"Move to\"><MenuItem key=\"archive\">Archive</MenuItem><MenuItem key=\"trash\">Trash</MenuItem></MenuItem></Menu></MenuTrigger>"
39
+ },
40
+ "variants": {
41
+ "placement": {
42
+ "bottom start": {
43
+ "use": "the default — below, aligned to the trigger's start edge"
44
+ },
45
+ "bottom end": {
46
+ "use": "a trigger at the end of a row or toolbar, so the menu does not run off the edge"
47
+ },
48
+ "top start": {
49
+ "use": "a trigger near the bottom of the viewport, such as a footer"
50
+ },
51
+ "top end": {
52
+ "use": "as top start, for a trigger at the end of its row"
53
+ }
54
+ }
55
+ },
56
+ "slots": {
57
+ "children": {
58
+ "accepts": [
59
+ "Button",
60
+ "Menu"
61
+ ],
62
+ "note": "the Button first, the Menu second"
63
+ }
64
+ },
65
+ "a11y": {
66
+ "role": "none — the Button gets aria-haspopup=\"menu\" and aria-expanded; the Menu is the menu",
67
+ "namesChild": true,
68
+ "guarantees": [
69
+ "Enter, Space and ArrowDown open the menu with focus on its first row; ArrowUp opens it on the last",
70
+ "choosing an action closes every open level and returns focus to the trigger; so do Escape and an outside click",
71
+ "the menu is named by the trigger unless it has its own aria-label",
72
+ "a submenu opens with the right arrow, Enter or Space and closes with the left arrow or Escape, which puts focus back on the row that opened it",
73
+ "submenus are non-modal: the parent menu stays on screen and hoverable, so the pointer can move between levels"
74
+ ],
75
+ "requires": [
76
+ "an icon-only trigger Button needs its own aria-label — it names the menu too"
77
+ ]
78
+ },
79
+ "antiPatterns": [
80
+ {
81
+ "dont": "wrapping a Menu in a Popover to make a dropdown",
82
+ "why": "the trigger then announces a dialog, not a menu, the arrow keys cannot open it, and choosing an action does not close it. That is exactly what MenuTrigger exists to replace"
83
+ },
84
+ {
85
+ "dont": "a plain <button> as the trigger",
86
+ "why": "the trigger must accept react-aria press props and a ref; IonBase's Button does, a DOM button silently never opens"
87
+ },
88
+ {
89
+ "dont": "more than two levels of submenu",
90
+ "why": "each level is a hover target narrower than the last; past two, the pointer falls off the path and the menu closes. Flatten with sections instead"
91
+ }
92
+ ],
93
+ "stylesheet": "src/styles/menu.css",
94
+ "tokens": [
95
+ "--border-focus",
96
+ "--border-strong",
97
+ "--border-subtle",
98
+ "--border-width-default",
99
+ "--border-width-thick",
100
+ "--font-family-sans",
101
+ "--font-weight-regular",
102
+ "--icon-default",
103
+ "--icon-disabled",
104
+ "--icon-primary",
105
+ "--icon-size-sm",
106
+ "--ion-duration-base",
107
+ "--ion-ease-out",
108
+ "--ion-menu-min-width",
109
+ "--ion-shadow-shadow-lg",
110
+ "--radius-sm",
111
+ "--radius-xl",
112
+ "--spacing-12",
113
+ "--spacing-2",
114
+ "--spacing-4",
115
+ "--spacing-40",
116
+ "--spacing-6",
117
+ "--spacing-8",
118
+ "--surface-default",
119
+ "--surface-hover",
120
+ "--surface-primary-subtle",
121
+ "--text-disabled",
122
+ "--text-primary",
123
+ "--text-secondary",
124
+ "--text-tertiary",
125
+ "--type-body",
126
+ "--type-body-line-height",
127
+ "--type-caption",
128
+ "--type-caption-line-height"
129
+ ],
130
+ "props": {
131
+ "children": {
132
+ "type": "[React.ReactElement<unknown, string | React.JSXElementConstructor<any>>, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>]",
133
+ "required": true,
134
+ "origin": "own",
135
+ "description": "Exactly two children: the Button that opens the menu, then the Menu. The\nButton must accept a ref and react-aria press props — IonBase's Button\ndoes; a plain `<button>` does not."
136
+ },
137
+ "placement": {
138
+ "type": "MenuTriggerPlacement | undefined",
139
+ "required": false,
140
+ "origin": "own",
141
+ "description": "Where the menu opens, relative to the trigger. A preference, not a\nguarantee: it flips when there is no room.",
142
+ "values": [
143
+ "bottom start",
144
+ "bottom end",
145
+ "top start",
146
+ "top end"
147
+ ]
148
+ },
149
+ "isDisabled": {
150
+ "type": "boolean | undefined",
151
+ "required": false,
152
+ "origin": "own",
153
+ "description": "Whether the trigger is disabled."
154
+ },
155
+ "isOpen": {
156
+ "type": "boolean | undefined",
157
+ "required": false,
158
+ "origin": "own"
159
+ },
160
+ "defaultOpen": {
161
+ "type": "boolean | undefined",
162
+ "required": false,
163
+ "origin": "own"
164
+ },
165
+ "onOpenChange": {
166
+ "type": "((isOpen: boolean) => void) | undefined",
167
+ "required": false,
168
+ "origin": "own"
169
+ }
170
+ },
171
+ "propCounts": {
172
+ "own": 6,
173
+ "aria": 0,
174
+ "dom": 0,
175
+ "other": 0
176
+ }
177
+ }