@redocly/theme 0.18.3-patch.1 → 0.18.3-patch.2

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.
@@ -30,7 +30,7 @@ function Catalog(props) {
30
30
  react_1.default.createElement(FilterTagsWrapper, null,
31
31
  react_1.default.createElement(Button_1.Button, { variant: "text", size: "small", icon: react_1.default.createElement(PlusIcon_1.PlusIcon, null), onClick: () => setIsAddingFilter(true) }, translate(translationKeys.addFilter, 'Add filter')),
32
32
  react_1.default.createElement(FilterTags_1.FilterTags, { filters: filters })),
33
- react_1.default.createElement(FilterContent_1.FilterContent, { setFilterTerm: setFilterTerm, filters: filters, filterTerm: filterTerm, isMobile: false }),
33
+ react_1.default.createElement(FilterContent_1.FilterContent, { setFilterTerm: setFilterTerm, filters: filters, filterTerm: filterTerm, isMobile: false, filterValuesCasing: catalogConfig.filterValuesCasing }),
34
34
  isAddingFilter && (react_1.default.createElement(FilterPopover_1.FilterPopover, { setIsAddingFilter: setIsAddingFilter, filters: filters })),
35
35
  react_1.default.createElement(CatalogPageContent, null,
36
36
  react_1.default.createElement(CatalogPageDescriptionWrapper, null,
@@ -1,7 +1,8 @@
1
1
  /// <reference types="react" />
2
2
  import type { ResolvedFilter } from '../../types/portal/src/shared/types/catalog';
3
- export declare function Filter({ filter }: {
3
+ export declare function Filter({ filter, filterValuesCasing, }: {
4
4
  filter: ResolvedFilter & {
5
5
  selectedOptions: any;
6
6
  };
7
+ filterValuesCasing?: 'sentence';
7
8
  }): JSX.Element | null;
@@ -10,7 +10,7 @@ const react_date_picker_1 = require("react-date-picker");
10
10
  const hooks_1 = require("../../mocks/hooks");
11
11
  const icons_1 = require("../../icons");
12
12
  const Select_1 = require("../../components/Select");
13
- function Filter({ filter }) {
13
+ function Filter({ filter, filterValuesCasing, }) {
14
14
  var _a, _b;
15
15
  const { translate } = (0, hooks_1.useTranslate)();
16
16
  const translationKeys = {
@@ -33,7 +33,7 @@ function Filter({ filter }) {
33
33
  selectOptions.push(...filter.filteredOptions.map((option) => ({
34
34
  value: option.value,
35
35
  component: (react_1.default.createElement(FilterOption, { key: option.value, role: "link", onClick: () => filter.selectOption(option.value) },
36
- react_1.default.createElement(FilterOptionLabel, null, translate(option.value)),
36
+ react_1.default.createElement(FilterOptionLabel, null, changeCasing(translate(option.value), filterValuesCasing)),
37
37
  react_1.default.createElement(FilterOptionCount, null, option.count))),
38
38
  })));
39
39
  }
@@ -48,7 +48,7 @@ function Filter({ filter }) {
48
48
  react_1.default.createElement(FilterTitle, null,
49
49
  translate(filter.titleTranslationKey, filter.title),
50
50
  " "),
51
- filter.type === 'select' ? (react_1.default.createElement(StyledSelect, { selected: selectedOptionComponent, options: selectOptions.map((option) => option.component) })) : filter.type === 'date-range' ? (react_1.default.createElement(react_1.default.Fragment, null,
51
+ filter.type === 'select' ? (react_1.default.createElement(StyledSelect, { withArrow: true, selected: selectedOptionComponent, options: selectOptions.map((option) => option.component) })) : filter.type === 'date-range' ? (react_1.default.createElement(react_1.default.Fragment, null,
52
52
  react_1.default.createElement(DatePickerWrapper, null,
53
53
  react_1.default.createElement("span", null, "From:"),
54
54
  react_1.default.createElement(react_date_picker_1.DatePicker, { closeCalendar: true, format: "y-MM-dd", dayPlaceholder: "DD", monthPlaceholder: "MM", yearPlaceholder: "YYYY", value: filter.selectedOptions.from ? new Date(filter.selectedOptions.from) : null, minDetail: "decade", maxDate: new Date(), onChange: (from) => {
@@ -66,11 +66,17 @@ function Filter({ filter }) {
66
66
  const id = 'filter--' + filter.property + '--' + slug(value.value);
67
67
  return (react_1.default.createElement(FilterOption, { key: id, role: "link", onClick: () => filter.toggleOption(value.value) },
68
68
  react_1.default.createElement(icons_1.CheckboxIcon, { checked: filter.selectedOptions.has(value.value) }),
69
- react_1.default.createElement(FilterOptionLabel, null, translate(value.value)),
69
+ react_1.default.createElement(FilterOptionLabel, null, changeCasing(translate(value.value), filterValuesCasing)),
70
70
  react_1.default.createElement(FilterOptionCount, null, value.count)));
71
71
  }))));
72
72
  }
73
73
  exports.Filter = Filter;
74
+ function changeCasing(str, casing) {
75
+ if (casing !== 'sentence')
76
+ return str;
77
+ const words = str.split(/[\s-_]+/);
78
+ return words.map((word) => word[0].toUpperCase() + word.slice(1).toLowerCase()).join(' ');
79
+ }
74
80
  const FilterGroup = styled_components_1.default.div `
75
81
  padding: var(--filter-group-padding);
76
82
  `;
@@ -5,7 +5,8 @@ interface FilterContentProps {
5
5
  filters: ResolvedFilter[];
6
6
  filterTerm: string;
7
7
  isMobile: boolean;
8
+ filterValuesCasing?: 'sentence';
8
9
  }
9
- export declare function FilterContent({ setFilterTerm, filters, filterTerm, isMobile, }: FilterContentProps): JSX.Element;
10
+ export declare function FilterContent({ setFilterTerm, filters, filterTerm, isMobile, filterValuesCasing, }: FilterContentProps): JSX.Element;
10
11
  export declare const FilterItems: import("styled-components").StyledComponent<"div", any, {}, never>;
11
12
  export {};
@@ -11,7 +11,7 @@ const hooks_1 = require("../../mocks/hooks");
11
11
  const Catalog_1 = require("../../components/Catalog");
12
12
  const index_1 = require("../../components/Filter/index");
13
13
  const FilterPopover_1 = require("../../components/Filter/FilterPopover");
14
- function FilterContent({ setFilterTerm, filters, filterTerm, isMobile, }) {
14
+ function FilterContent({ setFilterTerm, filters, filterTerm, isMobile, filterValuesCasing, }) {
15
15
  const { translate } = (0, hooks_1.useTranslate)();
16
16
  const translationKeys = {
17
17
  placeholder: 'theme.catalog.filters.placeholder',
@@ -31,7 +31,7 @@ function FilterContent({ setFilterTerm, filters, filterTerm, isMobile, }) {
31
31
  return (react_1.default.createElement(FilterContentWrapper, { isMobile: isMobile },
32
32
  react_1.default.createElement(Catalog_1.FilterControls, null,
33
33
  react_1.default.createElement(FilterPopover_1.StyledInput, { placeholder: translate(translationKeys.placeholder, 'Type to filter...'), value: filterTerm, onChange: (e) => setFilterTerm(e.target.value) })),
34
- react_1.default.createElement(exports.FilterItems, null, filters.map((filter, idx) => (react_1.default.createElement(index_1.Filter, { filter: filter, key: filter.property + '-' + idx })))),
34
+ react_1.default.createElement(exports.FilterItems, null, filters.map((filter, idx) => (react_1.default.createElement(index_1.Filter, { filter: filter, key: filter.property + '-' + idx, filterValuesCasing: filterValuesCasing })))),
35
35
  hasActiveFilters && (react_1.default.createElement(_theme_1.Button, { size: "small", onClick: handleClearAll }, translate(translationKeys.placeholder, 'Clear all filters')))));
36
36
  }
37
37
  exports.FilterContent = FilterContent;
@@ -15,9 +15,7 @@ export interface SelectProps {
15
15
  export declare const Select: ({ className, selected, options, dataAttributes, withArrow, triggerEvent, onChange, placement, alignment, icon, onlyIcon, }: SelectProps) => JSX.Element;
16
16
  export declare const SelectContainer: import("styled-components").StyledComponent<"div", any, {}, never>;
17
17
  export declare const SelectInput: import("styled-components").StyledComponent<"div", any, {}, never>;
18
- export declare const SelectInputValue: import("styled-components").StyledComponent<"div", any, {
19
- withIcon?: boolean | undefined;
20
- }, never>;
18
+ export declare const SelectInputValue: import("styled-components").StyledComponent<"div", any, {}, never>;
21
19
  export declare const SelectList: import("styled-components").StyledComponent<"ul", any, {
22
20
  placement?: string | undefined;
23
21
  alignment?: string | undefined;
@@ -56,7 +56,7 @@ const Select = ({ className, selected, options, dataAttributes, withArrow, trigg
56
56
  }, [selected]);
57
57
  return (react_1.default.createElement(exports.SelectContainer, Object.assign({}, dataAttributes, { "data-testid": "select", className: className, ref: selectRef, onPointerEnter: triggerEvent === 'hover' ? handleOpen : undefined, onPointerLeave: triggerEvent === 'hover' ? handleClose : undefined, onClick: triggerEvent === 'click' ? handleToggle : undefined }),
58
58
  react_1.default.createElement(exports.SelectInput, null,
59
- !onlyIcon && (react_1.default.createElement(exports.SelectInputValue, { withIcon: withArrow || !!icon }, _selected)),
59
+ !onlyIcon && react_1.default.createElement(exports.SelectInputValue, null, _selected),
60
60
  icon,
61
61
  withArrow ? isOpen ? react_1.default.createElement(icons_1.ArrowIcon, { direction: "up" }) : react_1.default.createElement(icons_1.ArrowIcon, { direction: "down" }) : null),
62
62
  isOpen && (react_1.default.createElement(exports.SelectList, { placement: placement, alignment: alignment }, options.map((option, index) => (react_1.default.createElement(exports.SelectListItem, { key: index, onClick: () => handleSelect(option) }, option)))))));
@@ -83,10 +83,11 @@ exports.SelectInput = styled_components_1.default.div `
83
83
  border-radius: var(--select-input-border-radius);
84
84
  padding: var(--select-input-padding-vertical) var(--select-input-padding-horizontal);
85
85
  cursor: pointer;
86
+ gap: 8px;
86
87
  `;
87
88
  exports.SelectInputValue = styled_components_1.default.div `
88
89
  pointer-events: none;
89
- ${({ withIcon }) => (withIcon ? 'margin-right: 8px;' : '')}
90
+ min-width: 0;
90
91
  `;
91
92
  exports.SelectList = styled_components_1.default.ul `
92
93
  position: absolute;
@@ -96,7 +97,9 @@ exports.SelectList = styled_components_1.default.ul `
96
97
  right: ${({ alignment }) => (alignment === 'end' ? '0' : 'auto')};
97
98
  margin: 0;
98
99
  min-width: var(--select-list-min-width);
100
+ width: 100%;
99
101
  max-width: var(--select-list-max-width);
102
+ ${({ placement }) => (!placement || placement === 'bottom' ? 'margin-top: 2px' : '')};
100
103
  padding: var(--select-list-padding);
101
104
  background-color: var(--select-list-background-color);
102
105
  border-radius: var(--select-list-border-radius);
@@ -10,8 +10,9 @@ interface TagProps {
10
10
  children?: React.ReactNode;
11
11
  closable?: boolean;
12
12
  color?: StatusColor | Color | string;
13
+ size?: string;
13
14
  icon?: React.ReactNode;
14
15
  onClick?: () => void;
15
16
  }
16
- export declare function Tag({ children, className, color, icon, onClick }: TagProps): JSX.Element;
17
+ export declare function Tag({ children, className, color, icon, onClick, size }: TagProps): JSX.Element;
17
18
  export {};
@@ -6,15 +6,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Tag = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
- function Tag({ children, className, color, icon, onClick }) {
10
- return (react_1.default.createElement(TagContainer, { className: className, color: color, onClick: onClick },
9
+ function Tag({ children, className, color, icon, onClick, size }) {
10
+ return (react_1.default.createElement(TagContainer, { className: className, color: color, size: size, onClick: onClick },
11
11
  icon ? icon : null,
12
12
  children));
13
13
  }
14
14
  exports.Tag = Tag;
15
- const TagContainer = styled_components_1.default.div.attrs(({ className, color }) => ({
15
+ const TagContainer = styled_components_1.default.div.attrs(({ className, color, size }) => ({
16
16
  'data-component-name': 'Tag/Tag',
17
- className: (className || '') + ` tag-default ${color ? `tag-${color}` : ''}`,
17
+ className: (className || '') +
18
+ ` tag-default ${color ? `tag-${color}` : ''} ${size ? `tag-size-${size}` : ''}`,
18
19
  })) `
19
20
  display: inline-flex;
20
21
  align-items: center;
@@ -23,6 +24,11 @@ const TagContainer = styled_components_1.default.div.attrs(({ className, color }
23
24
 
24
25
  padding: var(--tag-padding);
25
26
  margin: var(--tag-margin);
27
+
28
+ &:last-child {
29
+ margin-right: 0;
30
+ }
31
+
26
32
  gap: var(--tag-gap);
27
33
 
28
34
  font-size: var(--tag-font-size);
@@ -40,6 +40,11 @@ exports.tag = (0, styled_components_1.css) `
40
40
  * @tokens Tag colors
41
41
  */
42
42
 
43
+ .tag-size-large {
44
+ --tag-padding: 4px 16px;
45
+ --tag-font-size: var(--font-size-regular);
46
+ }
47
+
43
48
  .tag-grey,
44
49
  .tag-draft,
45
50
  .tag-schema,
package/lib/config.d.ts CHANGED
@@ -458,6 +458,10 @@ declare const catalogSchema: {
458
458
  readonly groupByFirstFilter: {
459
459
  readonly type: "boolean";
460
460
  };
461
+ readonly filterValuesCasing: {
462
+ readonly type: "string";
463
+ readonly enum: readonly ["sentence"];
464
+ };
461
465
  readonly items: {
462
466
  readonly type: "array";
463
467
  readonly items: {
@@ -1749,6 +1753,10 @@ export declare const themeConfigSchema: {
1749
1753
  readonly groupByFirstFilter: {
1750
1754
  readonly type: "boolean";
1751
1755
  };
1756
+ readonly filterValuesCasing: {
1757
+ readonly type: "string";
1758
+ readonly enum: readonly ["sentence"];
1759
+ };
1752
1760
  readonly items: {
1753
1761
  readonly type: "array";
1754
1762
  readonly items: {
package/lib/config.js CHANGED
@@ -332,6 +332,7 @@ const catalogSchema = {
332
332
  slug: { type: 'string' },
333
333
  filters: { type: 'array', items: catalogFilterSchema },
334
334
  groupByFirstFilter: { type: 'boolean' },
335
+ filterValuesCasing: { type: 'string', enum: ['sentence'] },
335
336
  items: navItemsSchema,
336
337
  requiredPermission: { type: 'string' },
337
338
  separateVersions: { type: 'boolean' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.18.3-patch.1",
3
+ "version": "0.18.3-patch.2",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -51,6 +51,7 @@ export default function Catalog(props: {
51
51
  filters={filters}
52
52
  filterTerm={filterTerm}
53
53
  isMobile={false}
54
+ filterValuesCasing={catalogConfig.filterValuesCasing}
54
55
  />
55
56
  {isAddingFilter && (
56
57
  <FilterPopover setIsAddingFilter={setIsAddingFilter} filters={filters} />
@@ -9,7 +9,13 @@ import { useTranslate } from '@portal/hooks';
9
9
  import { CheckboxIcon } from '@theme/icons';
10
10
  import { Select } from '@theme/components/Select';
11
11
 
12
- export function Filter({ filter }: { filter: ResolvedFilter & { selectedOptions: any } }) {
12
+ export function Filter({
13
+ filter,
14
+ filterValuesCasing,
15
+ }: {
16
+ filter: ResolvedFilter & { selectedOptions: any };
17
+ filterValuesCasing?: 'sentence';
18
+ }) {
13
19
  const { translate } = useTranslate();
14
20
  const translationKeys = {
15
21
  selectAll: 'theme.catalog.filters.select.all',
@@ -44,7 +50,9 @@ export function Filter({ filter }: { filter: ResolvedFilter & { selectedOptions:
44
50
  role="link"
45
51
  onClick={() => filter.selectOption(option.value)}
46
52
  >
47
- <FilterOptionLabel>{translate(option.value)}</FilterOptionLabel>
53
+ <FilterOptionLabel>
54
+ {changeCasing(translate(option.value), filterValuesCasing)}
55
+ </FilterOptionLabel>
48
56
  <FilterOptionCount>{option.count}</FilterOptionCount>
49
57
  </FilterOption>
50
58
  ),
@@ -65,6 +73,7 @@ export function Filter({ filter }: { filter: ResolvedFilter & { selectedOptions:
65
73
  <FilterTitle>{translate(filter.titleTranslationKey, filter.title)} </FilterTitle>
66
74
  {filter.type === 'select' ? (
67
75
  <StyledSelect
76
+ withArrow={true}
68
77
  selected={selectedOptionComponent}
69
78
  options={selectOptions.map((option) => option.component)}
70
79
  />
@@ -119,7 +128,9 @@ export function Filter({ filter }: { filter: ResolvedFilter & { selectedOptions:
119
128
  return (
120
129
  <FilterOption key={id} role="link" onClick={() => filter.toggleOption(value.value)}>
121
130
  <CheckboxIcon checked={filter.selectedOptions.has(value.value)} />
122
- <FilterOptionLabel>{translate(value.value)}</FilterOptionLabel>
131
+ <FilterOptionLabel>
132
+ {changeCasing(translate(value.value), filterValuesCasing)}
133
+ </FilterOptionLabel>
123
134
  <FilterOptionCount>{value.count}</FilterOptionCount>
124
135
  </FilterOption>
125
136
  );
@@ -129,6 +140,13 @@ export function Filter({ filter }: { filter: ResolvedFilter & { selectedOptions:
129
140
  );
130
141
  }
131
142
 
143
+ function changeCasing(str: string, casing?: 'sentence') {
144
+ if (casing !== 'sentence') return str;
145
+
146
+ const words = str.split(/[\s-_]+/);
147
+ return words.map((word) => word[0].toUpperCase() + word.slice(1).toLowerCase()).join(' ');
148
+ }
149
+
132
150
  const FilterGroup = styled.div`
133
151
  padding: var(--filter-group-padding);
134
152
  `;
@@ -13,6 +13,7 @@ interface FilterContentProps {
13
13
  filters: ResolvedFilter[];
14
14
  filterTerm: string;
15
15
  isMobile: boolean;
16
+ filterValuesCasing?: 'sentence';
16
17
  }
17
18
 
18
19
  export function FilterContent({
@@ -20,6 +21,7 @@ export function FilterContent({
20
21
  filters,
21
22
  filterTerm,
22
23
  isMobile,
24
+ filterValuesCasing,
23
25
  }: FilterContentProps) {
24
26
  const { translate } = useTranslate();
25
27
  const translationKeys = {
@@ -50,7 +52,11 @@ export function FilterContent({
50
52
  </FilterControls>
51
53
  <FilterItems>
52
54
  {filters.map((filter, idx) => (
53
- <Filter filter={filter} key={filter.property + '-' + idx} />
55
+ <Filter
56
+ filter={filter}
57
+ key={filter.property + '-' + idx}
58
+ filterValuesCasing={filterValuesCasing}
59
+ />
54
60
  ))}
55
61
  </FilterItems>
56
62
  {hasActiveFilters && (
@@ -72,9 +72,7 @@ export const Select = ({
72
72
  onClick={triggerEvent === 'click' ? handleToggle : undefined}
73
73
  >
74
74
  <SelectInput>
75
- {!onlyIcon && (
76
- <SelectInputValue withIcon={withArrow || !!icon}>{_selected}</SelectInputValue>
77
- )}
75
+ {!onlyIcon && <SelectInputValue>{_selected}</SelectInputValue>}
78
76
  {icon}
79
77
  {withArrow ? isOpen ? <ArrowIcon direction="up" /> : <ArrowIcon direction="down" /> : null}
80
78
  </SelectInput>
@@ -113,11 +111,12 @@ export const SelectInput = styled.div`
113
111
  border-radius: var(--select-input-border-radius);
114
112
  padding: var(--select-input-padding-vertical) var(--select-input-padding-horizontal);
115
113
  cursor: pointer;
114
+ gap: 8px;
116
115
  `;
117
116
 
118
- export const SelectInputValue = styled.div<{ withIcon?: boolean }>`
117
+ export const SelectInputValue = styled.div`
119
118
  pointer-events: none;
120
- ${({ withIcon }) => (withIcon ? 'margin-right: 8px;' : '')}
119
+ min-width: 0;
121
120
  `;
122
121
 
123
122
  export const SelectList = styled.ul<{ placement?: string; alignment?: string }>`
@@ -128,7 +127,9 @@ export const SelectList = styled.ul<{ placement?: string; alignment?: string }>`
128
127
  right: ${({ alignment }) => (alignment === 'end' ? '0' : 'auto')};
129
128
  margin: 0;
130
129
  min-width: var(--select-list-min-width);
130
+ width: 100%;
131
131
  max-width: var(--select-list-max-width);
132
+ ${({ placement }) => (!placement || placement === 'bottom' ? 'margin-top: 2px' : '')};
132
133
  padding: var(--select-list-padding);
133
134
  background-color: var(--select-list-background-color);
134
135
  border-radius: var(--select-list-border-radius);
@@ -24,22 +24,25 @@ interface TagProps {
24
24
  children?: React.ReactNode;
25
25
  closable?: boolean;
26
26
  color?: StatusColor | Color | string;
27
+ size?: string;
27
28
  icon?: React.ReactNode;
28
29
  onClick?: () => void;
29
30
  }
30
31
 
31
- export function Tag({ children, className, color, icon, onClick }: TagProps): JSX.Element {
32
+ export function Tag({ children, className, color, icon, onClick, size }: TagProps): JSX.Element {
32
33
  return (
33
- <TagContainer className={className} color={color} onClick={onClick}>
34
+ <TagContainer className={className} color={color} size={size} onClick={onClick}>
34
35
  {icon ? icon : null}
35
36
  {children}
36
37
  </TagContainer>
37
38
  );
38
39
  }
39
40
 
40
- const TagContainer = styled.div.attrs(({ className, color }: TagProps) => ({
41
+ const TagContainer = styled.div.attrs(({ className, color, size }: TagProps) => ({
41
42
  'data-component-name': 'Tag/Tag',
42
- className: (className || '') + ` tag-default ${color ? `tag-${color}` : ''}`,
43
+ className:
44
+ (className || '') +
45
+ ` tag-default ${color ? `tag-${color}` : ''} ${size ? `tag-size-${size}` : ''}`,
43
46
  }))<TagProps>`
44
47
  display: inline-flex;
45
48
  align-items: center;
@@ -48,6 +51,11 @@ const TagContainer = styled.div.attrs(({ className, color }: TagProps) => ({
48
51
 
49
52
  padding: var(--tag-padding);
50
53
  margin: var(--tag-margin);
54
+
55
+ &:last-child {
56
+ margin-right: 0;
57
+ }
58
+
51
59
  gap: var(--tag-gap);
52
60
 
53
61
  font-size: var(--tag-font-size);
@@ -38,6 +38,11 @@ export const tag = css`
38
38
  * @tokens Tag colors
39
39
  */
40
40
 
41
+ .tag-size-large {
42
+ --tag-padding: 4px 16px;
43
+ --tag-font-size: var(--font-size-regular);
44
+ }
45
+
41
46
  .tag-grey,
42
47
  .tag-draft,
43
48
  .tag-schema,
package/src/config.ts CHANGED
@@ -373,6 +373,7 @@ const catalogSchema = {
373
373
  slug: { type: 'string' },
374
374
  filters: { type: 'array', items: catalogFilterSchema },
375
375
  groupByFirstFilter: { type: 'boolean' },
376
+ filterValuesCasing: { type: 'string', enum: ['sentence'] },
376
377
  items: navItemsSchema,
377
378
  requiredPermission: { type: 'string' },
378
379
  separateVersions: { type: 'boolean' },