@spaced-out/ui-design-system 0.1.107 → 0.1.109

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.1.109](https://github.com/spaced-out/ui-design-system/compare/v0.1.108...v0.1.109) (2024-07-11)
6
+
7
+
8
+ ### Features
9
+
10
+ * 🆕 left and right icon support for link component through a conditional wrapper ([#239](https://github.com/spaced-out/ui-design-system/issues/239)) ([e1a4675](https://github.com/spaced-out/ui-design-system/commit/e1a4675c0852591a4ecbf00325dd91d3afead0d7))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * menu classnames support in simple menu derivatives ([#238](https://github.com/spaced-out/ui-design-system/issues/238)) ([d1925d7](https://github.com/spaced-out/ui-design-system/commit/d1925d72c9a4e55b9344a2e5bb035e6de9cfcea6))
16
+
17
+ ### [0.1.108](https://github.com/spaced-out/ui-design-system/compare/v0.1.107...v0.1.108) (2024-07-10)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * ✨ added required attribute in story for dropdowns ([#237](https://github.com/spaced-out/ui-design-system/issues/237)) ([cfd5f34](https://github.com/spaced-out/ui-design-system/commit/cfd5f3439977f4961ad92763b56debdfb76e70e9))
23
+
5
24
  ### [0.1.107](https://github.com/spaced-out/ui-design-system/compare/v0.1.106...v0.1.107) (2024-07-09)
6
25
 
7
26
 
@@ -29,6 +29,7 @@ const SimpleButtonDropdownBase = (props, ref) => {
29
29
  menuVirtualization,
30
30
  header,
31
31
  footer,
32
+ menuClassNames,
32
33
  clickAwayRef,
33
34
  ...buttonProps
34
35
  } = props;
@@ -76,7 +77,8 @@ const SimpleButtonDropdownBase = (props, ref) => {
76
77
  size,
77
78
  virtualization: menuVirtualization,
78
79
  header,
79
- footer
80
+ footer,
81
+ classNames: menuClassNames
80
82
  }
81
83
  }), optionsVariant === 'checkbox' ? btnText : children);
82
84
  };
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from 'react';
4
4
 
5
+ import type {MenuClassNames} from '../../types/menu';
5
6
  import type {ClickAwayRefType} from '../../utils/click-away';
6
7
  import {
7
8
  getButtonLabelFromSelectedKeys,
@@ -41,6 +42,7 @@ export type SimpleButtonDropdownProps = {
41
42
  menuVirtualization?: Virtualization,
42
43
  header?: React.Node,
43
44
  footer?: React.Node,
45
+ menuClassNames?: MenuClassNames,
44
46
 
45
47
  // events
46
48
  onOptionSelect?: (option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed,
@@ -73,6 +75,7 @@ const SimpleButtonDropdownBase = (props: SimpleButtonDropdownProps, ref) => {
73
75
  menuVirtualization,
74
76
  header,
75
77
  footer,
78
+ menuClassNames,
76
79
  clickAwayRef,
77
80
  ...buttonProps
78
81
  } = props;
@@ -140,6 +143,7 @@ const SimpleButtonDropdownBase = (props: SimpleButtonDropdownProps, ref) => {
140
143
  virtualization: menuVirtualization,
141
144
  header,
142
145
  footer,
146
+ classNames: menuClassNames,
143
147
  }}
144
148
  >
145
149
  {optionsVariant === 'checkbox' ? btnText : children}
@@ -28,6 +28,7 @@ const SimpleDropdownBase = (props, ref) => {
28
28
  isMenuFluid = true,
29
29
  header,
30
30
  footer,
31
+ menuClassNames,
31
32
  clickAwayRef,
32
33
  ...inputProps
33
34
  } = props;
@@ -76,7 +77,8 @@ const SimpleDropdownBase = (props, ref) => {
76
77
  size,
77
78
  isFluid: isMenuFluid,
78
79
  header,
79
- footer
80
+ footer,
81
+ classNames: menuClassNames
80
82
  }
81
83
  }));
82
84
  };
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from 'react';
4
4
 
5
+ import type {MenuClassNames} from '../../types/menu';
5
6
  import type {ClickAwayRefType} from '../../utils/click-away';
6
7
  import {
7
8
  getSelectedKeysFromSelectedOption,
@@ -38,6 +39,7 @@ export type SimpleDropdownProps = {
38
39
  menuVirtualization?: Virtualization,
39
40
  header?: React.Node,
40
41
  footer?: React.Node,
42
+ menuClassNames?: MenuClassNames,
41
43
 
42
44
  // events
43
45
  onChange?: (option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed,
@@ -68,6 +70,7 @@ const SimpleDropdownBase = (props: SimpleDropdownProps, ref) => {
68
70
  isMenuFluid = true,
69
71
  header,
70
72
  footer,
73
+ menuClassNames,
71
74
  clickAwayRef,
72
75
  ...inputProps
73
76
  } = props;
@@ -136,6 +139,7 @@ const SimpleDropdownBase = (props: SimpleDropdownProps, ref) => {
136
139
  isFluid: isMenuFluid,
137
140
  header,
138
141
  footer,
142
+ classNames: menuClassNames,
139
143
  }}
140
144
  />
141
145
  );
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.Icon = exports.ICON_SIZE = void 0;
6
+ exports.Icon = exports.ICON_TYPE = exports.ICON_SIZE = void 0;
7
7
  var React = _interopRequireWildcard(require("react"));
8
8
  var _typography = require("../../types/typography");
9
9
  var _classify = _interopRequireDefault(require("../../utils/classify"));
@@ -12,6 +12,13 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
12
12
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
13
13
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
14
14
 
15
+ const ICON_TYPE = Object.freeze({
16
+ regular: 'regular',
17
+ solid: 'solid',
18
+ duotone: 'duotone',
19
+ brands: 'brands'
20
+ });
21
+ exports.ICON_TYPE = ICON_TYPE;
15
22
  const ICON_SIZE = Object.freeze({
16
23
  small: 'small',
17
24
  medium: 'medium',
@@ -9,7 +9,12 @@ import classify from '../../utils/classify';
9
9
  import typographyStyle from '../../styles/typography.module.css';
10
10
 
11
11
 
12
- export type IconType = 'regular' | 'solid' | 'duotone' | 'brands';
12
+ export const ICON_TYPE = Object.freeze({
13
+ regular: 'regular',
14
+ solid: 'solid',
15
+ duotone: 'duotone',
16
+ brands: 'brands',
17
+ });
13
18
 
14
19
  export const ICON_SIZE = Object.freeze({
15
20
  small: 'small',
@@ -18,6 +23,7 @@ export const ICON_SIZE = Object.freeze({
18
23
  });
19
24
 
20
25
  export type IconSize = $Keys<typeof ICON_SIZE>;
26
+ export type IconType = $Keys<typeof ICON_TYPE>;
21
27
 
22
28
  export type IconProps = {
23
29
  type?: IconType,
@@ -29,6 +29,7 @@ const SimpleInlineDropdownBase = (props, ref) => {
29
29
  menuVirtualization,
30
30
  header,
31
31
  footer,
32
+ menuClassNames,
32
33
  clickAwayRef,
33
34
  ...buttonProps
34
35
  } = props;
@@ -76,7 +77,8 @@ const SimpleInlineDropdownBase = (props, ref) => {
76
77
  size: size === 'extraSmall' ? 'small' : size,
77
78
  virtualization: menuVirtualization,
78
79
  header,
79
- footer
80
+ footer,
81
+ classNames: menuClassNames
80
82
  }
81
83
  }), optionsVariant === 'checkbox' ? btnText : children);
82
84
  };
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from 'react';
4
4
 
5
+ import type {MenuClassNames} from '../../types/menu';
5
6
  import type {ClickAwayRefType} from '../../utils/click-away';
6
7
  import {
7
8
  getButtonLabelFromSelectedKeys,
@@ -44,6 +45,7 @@ export type SimpleInlineDropdownProps = {
44
45
  menuVirtualization?: Virtualization,
45
46
  header?: React.Node,
46
47
  footer?: React.Node,
48
+ menuClassNames?: MenuClassNames,
47
49
 
48
50
  // Resolvers
49
51
  resolveLabel?: (option: MenuOption) => string | React.Node,
@@ -71,6 +73,7 @@ const SimpleInlineDropdownBase = (props: SimpleInlineDropdownProps, ref) => {
71
73
  menuVirtualization,
72
74
  header,
73
75
  footer,
76
+ menuClassNames,
74
77
  clickAwayRef,
75
78
  ...buttonProps
76
79
  } = props;
@@ -138,6 +141,7 @@ const SimpleInlineDropdownBase = (props: SimpleInlineDropdownProps, ref) => {
138
141
  virtualization: menuVirtualization,
139
142
  header,
140
143
  footer,
144
+ classNames: menuClassNames,
141
145
  }}
142
146
  >
143
147
  {optionsVariant === 'checkbox' ? btnText : children}
@@ -7,6 +7,8 @@ exports.Link = exports.LINK_AS = exports.ANCHOR_TARGET = exports.ANCHOR_REL = vo
7
7
  var React = _interopRequireWildcard(require("react"));
8
8
  var _typography = require("../../types/typography");
9
9
  var _classify = _interopRequireDefault(require("../../utils/classify"));
10
+ var _ConditionalWrapper = require("../ConditionalWrapper");
11
+ var _Icon = require("../Icon");
10
12
  var _typographyModule = _interopRequireDefault(require("../../styles/typography.module.css"));
11
13
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
14
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
@@ -65,6 +67,12 @@ const Link = /*#__PURE__*/React.forwardRef((_ref, ref) => {
65
67
  disabled,
66
68
  onClick,
67
69
  linkComponent: LinkComponent = DefaultLink,
70
+ iconLeftName,
71
+ iconLeftSize = _Icon.ICON_SIZE.small,
72
+ iconLeftType,
73
+ iconRightName,
74
+ iconRightSize = _Icon.ICON_SIZE.small,
75
+ iconRightType,
68
76
  ...props
69
77
  } = _ref;
70
78
  const linkRef = React.useRef(null);
@@ -98,12 +106,29 @@ const Link = /*#__PURE__*/React.forwardRef((_ref, ref) => {
98
106
  ref: linkRef,
99
107
  "data-testid": "Link",
100
108
  className: (0, _classify.default)(_typographyModule.default.link, _typographyModule.default[as], _typographyModule.default[color], {
101
- [_typographyModule.default.underline]: underline,
109
+ [_typographyModule.default.underline]: underline && !(iconLeftName || iconRightName),
102
110
  [_typographyModule.default.disabled]: disabled
103
111
  }, className),
104
112
  onClick: handleClick,
105
113
  onKeyPress: handleKeyPress
106
- }), children);
114
+ }), !!iconLeftName && /*#__PURE__*/React.createElement(_Icon.Icon, {
115
+ name: iconLeftName,
116
+ size: iconLeftSize,
117
+ type: iconLeftType,
118
+ className: _typographyModule.default[color]
119
+ }), /*#__PURE__*/React.createElement(_ConditionalWrapper.ConditionalWrapper, {
120
+ condition: Boolean(iconLeftName || iconRightName),
121
+ wrapper: children => /*#__PURE__*/React.createElement("span", {
122
+ className: (0, _classify.default)({
123
+ [_typographyModule.default.underline]: underline
124
+ })
125
+ }, children)
126
+ }, children), !!iconRightName && /*#__PURE__*/React.createElement(_Icon.Icon, {
127
+ name: iconRightName,
128
+ size: iconRightSize,
129
+ type: iconRightType,
130
+ className: _typographyModule.default[color]
131
+ }));
107
132
  });
108
133
  exports.Link = Link;
109
134
  const DefaultLink = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
@@ -5,6 +5,9 @@ import * as React from 'react';
5
5
  import type {ColorTypes} from '../../types/typography';
6
6
  import {TEXT_COLORS} from '../../types/typography';
7
7
  import classify from '../../utils/classify';
8
+ import {ConditionalWrapper} from '../ConditionalWrapper';
9
+ import type {IconSize, IconType} from '../Icon';
10
+ import {Icon, ICON_SIZE} from '../Icon';
8
11
 
9
12
  import css from '../../styles/typography.module.css';
10
13
 
@@ -66,6 +69,12 @@ export type BaseLinkProps = {
66
69
  as?: LinkAs,
67
70
  rel?: AnchorRel,
68
71
  target?: AnchorTarget,
72
+ iconLeftName?: string,
73
+ iconLeftSize?: IconSize,
74
+ iconLeftType?: IconType,
75
+ iconRightName?: string,
76
+ iconRightSize?: IconSize,
77
+ iconRightType?: IconType,
69
78
  /**
70
79
  * IMPORTANT: If you are using `to` make sure to provide link component from your router
71
80
  * if you want to prevent full page reloads in a Single Page Application (SPA).
@@ -114,6 +123,12 @@ export const Link: React$AbstractComponent<LinkProps, ?HTMLAnchorElement> =
114
123
  disabled,
115
124
  onClick,
116
125
  linkComponent: LinkComponent = DefaultLink,
126
+ iconLeftName,
127
+ iconLeftSize = ICON_SIZE.small,
128
+ iconLeftType,
129
+ iconRightName,
130
+ iconRightSize = ICON_SIZE.small,
131
+ iconRightType,
117
132
  ...props
118
133
  }: LinkProps,
119
134
  ref,
@@ -158,7 +173,7 @@ export const Link: React$AbstractComponent<LinkProps, ?HTMLAnchorElement> =
158
173
  css[as],
159
174
  css[color],
160
175
  {
161
- [css.underline]: underline,
176
+ [css.underline]: underline && !(iconLeftName || iconRightName),
162
177
  [css.disabled]: disabled,
163
178
  },
164
179
  className,
@@ -166,7 +181,38 @@ export const Link: React$AbstractComponent<LinkProps, ?HTMLAnchorElement> =
166
181
  onClick={handleClick}
167
182
  onKeyPress={handleKeyPress}
168
183
  >
169
- {children}
184
+ {!!iconLeftName && (
185
+ <Icon
186
+ name={iconLeftName}
187
+ size={iconLeftSize}
188
+ type={iconLeftType}
189
+ className={css[color]}
190
+ />
191
+ )}
192
+
193
+ <ConditionalWrapper
194
+ condition={Boolean(iconLeftName || iconRightName)}
195
+ wrapper={(children) => (
196
+ <span
197
+ className={classify({
198
+ [css.underline]: underline,
199
+ })}
200
+ >
201
+ {children}
202
+ </span>
203
+ )}
204
+ >
205
+ {children}
206
+ </ConditionalWrapper>
207
+
208
+ {!!iconRightName && (
209
+ <Icon
210
+ name={iconRightName}
211
+ size={iconRightSize}
212
+ type={iconRightType}
213
+ className={css[color]}
214
+ />
215
+ )}
170
216
  </LinkComponent>
171
217
  );
172
218
  },
@@ -3,6 +3,7 @@ import * as React from 'react';
3
3
  // $FlowIssue[nonstrict-import] react-window
4
4
  import {FixedSizeList} from 'react-window';
5
5
 
6
+ import type {MenuClassNames} from '../../types/menu';
6
7
  import {classify} from '../../utils/classify';
7
8
  import {
8
9
  getFilteredComposeOptionsFromSearchText,
@@ -21,13 +22,7 @@ import {MenuOptionButton} from './MenuOptionButton';
21
22
  import css from './Menu.module.css';
22
23
 
23
24
 
24
- type ClassNames = $ReadOnly<{
25
- wrapper?: string,
26
- option?: string,
27
- groupTitle?: string,
28
- optionTextContainer?: string,
29
- optionTextLabel?: string,
30
- }>;
25
+ type ClassNames = MenuClassNames;
31
26
 
32
27
  type OptionClassNames = $ReadOnly<{
33
28
  wrapper?: string,
@@ -31,6 +31,7 @@ const SimpleOptionButtonBase = (props, ref) => {
31
31
  menuVirtualization,
32
32
  header,
33
33
  footer,
34
+ menuClassNames,
34
35
  clickAwayRef,
35
36
  ...buttonProps
36
37
  } = props;
@@ -75,7 +76,8 @@ const SimpleOptionButtonBase = (props, ref) => {
75
76
  size: menuSize,
76
77
  virtualization: menuVirtualization,
77
78
  header,
78
- footer
79
+ footer,
80
+ classNames: menuClassNames
79
81
  }
80
82
  }), children);
81
83
  };
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from 'react';
4
4
 
5
+ import type {MenuClassNames} from '../../types/menu';
5
6
  import type {ClickAwayRefType} from '../../utils/click-away';
6
7
  import {getSelectedKeysFromSelectedOption} from '../../utils/menu';
7
8
  import type {ButtonProps} from '../Button';
@@ -43,6 +44,7 @@ export type SimpleOptionButtonProps = {
43
44
  menuVirtualization?: Virtualization,
44
45
  header?: React.Node,
45
46
  footer?: React.Node,
47
+ menuClassNames?: MenuClassNames,
46
48
 
47
49
  // events
48
50
  onOptionSelect?: (option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed,
@@ -78,6 +80,7 @@ const SimpleOptionButtonBase = (props: SimpleOptionButtonProps, ref) => {
78
80
  menuVirtualization,
79
81
  header,
80
82
  footer,
83
+ menuClassNames,
81
84
  clickAwayRef,
82
85
  ...buttonProps
83
86
  } = props;
@@ -137,6 +140,7 @@ const SimpleOptionButtonBase = (props: SimpleOptionButtonProps, ref) => {
137
140
  virtualization: menuVirtualization,
138
141
  header,
139
142
  footer,
143
+ classNames: menuClassNames,
140
144
  }}
141
145
  >
142
146
  {children}
@@ -27,6 +27,7 @@ const SimpleTypeaheadBase = (props, ref) => {
27
27
  menuVirtualization,
28
28
  header,
29
29
  footer,
30
+ menuClassNames,
30
31
  clickAwayRef,
31
32
  ...inputProps
32
33
  } = props;
@@ -75,7 +76,8 @@ const SimpleTypeaheadBase = (props, ref) => {
75
76
  size,
76
77
  virtualization: menuVirtualization,
77
78
  header,
78
- footer
79
+ footer,
80
+ classNames: menuClassNames
79
81
  },
80
82
  clickAwayRef: clickAwayRef
81
83
  }));
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from 'react';
4
4
 
5
+ import type {MenuClassNames} from '../../types/menu';
5
6
  import type {ClickAwayRefType} from '../../utils/click-away';
6
7
  import {getTextLabelFromSelectedKeys} from '../../utils/menu';
7
8
  import type {InputProps} from '../Input';
@@ -34,6 +35,7 @@ export type SimpleTypeaheadProps = {
34
35
  menuVirtualization?: Virtualization,
35
36
  header?: React.Node,
36
37
  footer?: React.Node,
38
+ menuClassNames?: MenuClassNames,
37
39
 
38
40
  // events
39
41
  onSelect?: (option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed,
@@ -66,6 +68,7 @@ const SimpleTypeaheadBase = (props: SimpleTypeaheadProps, ref) => {
66
68
  menuVirtualization,
67
69
  header,
68
70
  footer,
71
+ menuClassNames,
69
72
  clickAwayRef,
70
73
  ...inputProps
71
74
  } = props;
@@ -133,6 +136,7 @@ const SimpleTypeaheadBase = (props: SimpleTypeaheadProps, ref) => {
133
136
  virtualization: menuVirtualization,
134
137
  header,
135
138
  footer,
139
+ classNames: menuClassNames,
136
140
  }}
137
141
  clickAwayRef={clickAwayRef}
138
142
  />
@@ -47,7 +47,7 @@
47
47
 
48
48
  @value (size36, size24, size18) from 'variables/_size.css';
49
49
 
50
- @value (spaceXXSmall, spaceNone) from 'variables/_space.css';
50
+ @value (spaceXSmall, spaceXXSmall, spaceNone) from 'variables/_space.css';
51
51
 
52
52
  @value (borderWidthNone, borderWidthTertiary, borderRadiusXSmall) from 'variables/_border.css';
53
53
 
@@ -293,6 +293,7 @@
293
293
  border-radius: calc(borderRadiusXSmall / 2);
294
294
  cursor: pointer;
295
295
  width: fit-content;
296
+ gap: spaceXSmall;
296
297
  }
297
298
 
298
299
  .underline {
@@ -25,6 +25,17 @@ Object.keys(_common).forEach(function (key) {
25
25
  }
26
26
  });
27
27
  });
28
+ var _menu = require("./menu");
29
+ Object.keys(_menu).forEach(function (key) {
30
+ if (key === "default" || key === "__esModule") return;
31
+ if (key in exports && exports[key] === _menu[key]) return;
32
+ Object.defineProperty(exports, key, {
33
+ enumerable: true,
34
+ get: function () {
35
+ return _menu[key];
36
+ }
37
+ });
38
+ });
28
39
  var _toast = require("./toast");
29
40
  Object.keys(_toast).forEach(function (key) {
30
41
  if (key === "default" || key === "__esModule") return;
@@ -2,5 +2,6 @@
2
2
 
3
3
  export * from './charts';
4
4
  export * from './common';
5
+ export * from './menu';
5
6
  export * from './toast';
6
7
  export * from './typography';
File without changes
@@ -0,0 +1,9 @@
1
+ // @flow strict
2
+
3
+ export type MenuClassNames = $ReadOnly<{
4
+ wrapper?: string,
5
+ option?: string,
6
+ groupTitle?: string,
7
+ optionTextContainer?: string,
8
+ optionTextLabel?: string,
9
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaced-out/ui-design-system",
3
- "version": "0.1.107",
3
+ "version": "0.1.109",
4
4
  "main": "index.js",
5
5
  "description": "Sense UI components library",
6
6
  "author": {