From 4dd58c5dcb22f4c0456a36d6c38883b54d8a7c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20K=C3=BCttner?= Date: Sun, 17 May 2020 15:47:07 +0200 Subject: [PATCH] feat(widget-list): add hiding list content with minimize_collapsed option (#3607) --- .../src/ListControl.js | 93 +- .../src/__tests__/ListControl.spec.js | 174 ++ .../__snapshots__/ListControl.spec.js.snap | 1680 ++++++++++++++++- website/content/docs/widgets/list.md | 11 + 4 files changed, 1925 insertions(+), 33 deletions(-) diff --git a/packages/netlify-cms-widget-list/src/ListControl.js b/packages/netlify-cms-widget-list/src/ListControl.js index 85d08875..c743dc9c 100644 --- a/packages/netlify-cms-widget-list/src/ListControl.js +++ b/packages/netlify-cms-widget-list/src/ListControl.js @@ -115,14 +115,15 @@ export default class ListControl extends React.Component { constructor(props) { super(props); const { field, value } = props; - const allItemsCollapsed = field.get('collapsed', true); - const itemsCollapsed = value && Array(value.size).fill(allItemsCollapsed); - const keys = value && Array.from({ length: value.size }, () => uuid()); + const listCollapsed = field.get('collapsed', true); + const itemsCollapsed = (value && Array(value.size).fill(listCollapsed)) || []; + const keys = (value && Array.from({ length: value.size }, () => uuid())) || []; this.state = { - itemsCollapsed: List(itemsCollapsed), + listCollapsed, + itemsCollapsed, value: valueToString(value), - keys: List(keys), + keys, }; } @@ -184,8 +185,8 @@ export default class ListControl extends React.Component { ? this.singleDefault() : fromJS(this.multipleDefault(field.get('fields'))); this.setState({ - itemsCollapsed: this.state.itemsCollapsed.push(false), - keys: this.state.keys.push(uuid()), + itemsCollapsed: [...this.state.itemsCollapsed, false], + keys: [...this.state.keys, uuid()], }); onChange((value || List()).push(parsedValue)); }; @@ -202,8 +203,8 @@ export default class ListControl extends React.Component { const { value, onChange } = this.props; const parsedValue = fromJS(this.mixedDefault(typeKey, type)); this.setState({ - itemsCollapsed: this.state.itemsCollapsed.push(false), - keys: this.state.keys.push(uuid()), + itemsCollapsed: [...this.state.itemsCollapsed, false], + keys: [...this.state.keys, uuid()], }); onChange((value || List()).push(parsedValue)); }; @@ -300,7 +301,10 @@ export default class ListControl extends React.Component { ? { [collectionName]: metadata.removeIn(metadataRemovePath) } : metadata; - this.setState({ itemsCollapsed: itemsCollapsed.delete(index), keys: keys.delete(index) }); + itemsCollapsed.splice(index, 1); + keys.splice(index, 1); + + this.setState({ itemsCollapsed: [...itemsCollapsed], keys: [...keys] }); onChange(value.remove(index), parsedMetadata); clearFieldErrors(); @@ -314,16 +318,35 @@ export default class ListControl extends React.Component { handleItemCollapseToggle = (index, event) => { event.preventDefault(); const { itemsCollapsed } = this.state; - const collapsed = itemsCollapsed.get(index); - this.setState({ itemsCollapsed: itemsCollapsed.set(index, !collapsed) }); + const newItemsCollapsed = itemsCollapsed.map((collapsed, itemIndex) => { + if (index === itemIndex) { + return !collapsed; + } + return collapsed; + }); + this.setState({ + itemsCollapsed: newItemsCollapsed, + }); }; handleCollapseAllToggle = e => { e.preventDefault(); - const { value } = this.props; - const { itemsCollapsed } = this.state; + const { value, field } = this.props; + const { itemsCollapsed, listCollapsed } = this.state; + const minimizeCollapsedItems = field.get('minimize_collapsed', false); + const listCollapsedByDefault = field.get('collapsed', true); const allItemsCollapsed = itemsCollapsed.every(val => val === true); - this.setState({ itemsCollapsed: List(Array(value.size).fill(!allItemsCollapsed)) }); + + if (minimizeCollapsedItems) { + let updatedItemsCollapsed = itemsCollapsed; + // Only allow collapsing all items in this mode but not opening all at once + if (!listCollapsed || !listCollapsedByDefault) { + updatedItemsCollapsed = Array(value.size).fill(!listCollapsed); + } + this.setState({ listCollapsed: !listCollapsed, itemsCollapsed: updatedItemsCollapsed }); + } else { + this.setState({ itemsCollapsed: Array(value.size).fill(!allItemsCollapsed) }); + } }; objectLabel(item) { @@ -368,11 +391,18 @@ export default class ListControl extends React.Component { this.props.onChange(newValue); // Update collapsing - const collapsed = itemsCollapsed.get(oldIndex); - const updatedItemsCollapsed = itemsCollapsed.delete(oldIndex).insert(newIndex, collapsed); + const collapsed = itemsCollapsed[oldIndex]; + itemsCollapsed.splice(oldIndex, 1); + const updatedItemsCollapsed = [...itemsCollapsed]; + updatedItemsCollapsed.splice(newIndex, 0, collapsed); // Reset item to ensure updated state - const updatedKeys = keys.set(oldIndex, uuid()).set(newIndex, uuid()); + const updatedKeys = keys.map((key, keyIndex) => { + if (keyIndex === oldIndex || keyIndex === newIndex) { + return uuid(); + } + return key; + }); this.setState({ itemsCollapsed: updatedItemsCollapsed, keys: updatedKeys }); //clear error fields and remove old validations @@ -394,8 +424,8 @@ export default class ListControl extends React.Component { } = this.props; const { itemsCollapsed, keys } = this.state; - const collapsed = itemsCollapsed.get(index); - const key = keys.get(index); + const collapsed = itemsCollapsed[index]; + const key = keys[index]; let field = this.props.field; if (this.getValueType() === valueTypes.MIXED) { @@ -473,11 +503,14 @@ export default class ListControl extends React.Component { renderListControl() { const { value, forID, field, classNameWrapper } = this.props; - const { itemsCollapsed } = this.state; + const { itemsCollapsed, listCollapsed } = this.state; const items = value || List(); const label = field.get('label', field.get('name')); const labelSingular = field.get('label_singular') || field.get('label', field.get('name')); const listLabel = items.size === 1 ? labelSingular.toLowerCase() : label.toLowerCase(); + const minimizeCollapsedItems = field.get('minimize_collapsed', false); + const allItemsCollapsed = itemsCollapsed.every(val => val === true); + const selfCollapsed = allItemsCollapsed && (listCollapsed || !minimizeCollapsedItems); return ( @@ -499,15 +532,17 @@ export default class ListControl extends React.Component { heading={`${items.size} ${listLabel}`} label={labelSingular.toLowerCase()} onCollapseToggle={this.handleCollapseAllToggle} - collapsed={itemsCollapsed.every(val => val === true)} - /> - + {(!selfCollapsed || !minimizeCollapsedItems) && ( + + )} )} diff --git a/packages/netlify-cms-widget-list/src/__tests__/ListControl.spec.js b/packages/netlify-cms-widget-list/src/__tests__/ListControl.spec.js index 2a9ae0a6..9da67098 100644 --- a/packages/netlify-cms-widget-list/src/__tests__/ListControl.spec.js +++ b/packages/netlify-cms-widget-list/src/__tests__/ListControl.spec.js @@ -20,6 +20,7 @@ jest.mock('netlify-cms-ui-default', () => { const actual = jest.requireActual('netlify-cms-ui-default'); const ListItemTopBar = props => ( + {props.children} ); @@ -454,4 +455,177 @@ describe('ListControl', () => { ); expect(getByText('hello - world - index.md')).toBeInTheDocument(); }); + + it('should render list with fields with default collapse ("true") and minimize_collapsed ("false")', () => { + const field = fromJS({ + name: 'list', + label: 'List', + fields: [{ label: 'String', name: 'string', widget: 'string' }], + }); + const { asFragment, getByTestId } = render( + , + ); + + expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true'); + expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true'); + + expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true'); + expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true'); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render list with fields with collapse = "false" and default minimize_collapsed ("false")', () => { + const field = fromJS({ + name: 'list', + label: 'List', + collapsed: false, + fields: [{ label: 'String', name: 'string', widget: 'string' }], + }); + const { asFragment, getByTestId } = render( + , + ); + + expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false'); + expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false'); + + expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false'); + expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false'); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render list with fields with default collapse ("true") and minimize_collapsed = "true"', () => { + const field = fromJS({ + name: 'list', + label: 'List', + minimize_collapsed: true, + fields: [{ label: 'String', name: 'string', widget: 'string' }], + }); + const { asFragment, getByTestId, queryByTestId } = render( + , + ); + + expect(queryByTestId('styled-list-item-top-bar-0')).toBeNull(); + expect(queryByTestId('styled-list-item-top-bar-1')).toBeNull(); + + expect(queryByTestId('object-control-0')).toBeNull(); + expect(queryByTestId('object-control-1')).toBeNull(); + + expect(asFragment()).toMatchSnapshot(); + + fireEvent.click(getByTestId('expand-button')); + + expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true'); + expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true'); + + expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true'); + expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true'); + }); + + it('should render list with fields with collapse = "false" and default minimize_collapsed = "true"', () => { + const field = fromJS({ + name: 'list', + label: 'List', + collapsed: false, + minimize_collapsed: true, + fields: [{ label: 'String', name: 'string', widget: 'string' }], + }); + const { asFragment, getByTestId, queryByTestId } = render( + , + ); + + expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false'); + expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false'); + + expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false'); + expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false'); + + expect(asFragment()).toMatchSnapshot(); + + fireEvent.click(getByTestId('expand-button')); + + expect(queryByTestId('styled-list-item-top-bar-0')).toBeNull(); + expect(queryByTestId('styled-list-item-top-bar-1')).toBeNull(); + + expect(queryByTestId('object-control-0')).toBeNull(); + expect(queryByTestId('object-control-1')).toBeNull(); + }); + + it('should add to list when add button is clicked', () => { + const field = fromJS({ + name: 'list', + label: 'List', + fields: [{ label: 'String', name: 'string', widget: 'string' }], + }); + const { asFragment, getByText, queryByTestId, rerender, getByTestId } = render( + , + ); + + expect(queryByTestId('object-control-0')).toBeNull(); + + fireEvent.click(getByText('Add list')); + + expect(props.onChange).toHaveBeenCalledTimes(1); + expect(props.onChange).toHaveBeenCalledWith(fromJS([{}])); + + rerender(); + + expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false'); + expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false'); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('should remove from list when remove button is clicked', () => { + const field = fromJS({ + name: 'list', + label: 'List', + collapsed: false, + minimize_collapsed: true, + fields: [{ label: 'String', name: 'string', widget: 'string' }], + }); + const { asFragment, getAllByText, rerender } = render( + , + ); + + expect(asFragment()).toMatchSnapshot(); + + let mock; + try { + mock = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + const items = getAllByText('Remove'); + fireEvent.click(items[0]); + + expect(props.onChange).toHaveBeenCalledTimes(1); + expect(props.onChange).toHaveBeenCalledWith(fromJS([{ string: 'item 2' }]), undefined); + + rerender(); + + expect(asFragment()).toMatchSnapshot(); + } finally { + mock.mockRestore(); + } + }); }); diff --git a/packages/netlify-cms-widget-list/src/__tests__/__snapshots__/ListControl.spec.js.snap b/packages/netlify-cms-widget-list/src/__tests__/__snapshots__/ListControl.spec.js.snap index 167e1099..4179fb63 100644 --- a/packages/netlify-cms-widget-list/src/__tests__/__snapshots__/ListControl.spec.js.snap +++ b/packages/netlify-cms-widget-list/src/__tests__/__snapshots__/ListControl.spec.js.snap @@ -1,5 +1,715 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`ListControl should add to list when add button is clicked 1`] = ` + + .emotion-18 { + padding: 0 14px 14px; +} + +.emotion-12 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #dfdfe3; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + margin: 0 -14px; + padding: 13px; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + font-size: 14px; + font-weight: 500; + line-height: 1; +} + +.emotion-3 { + border: 0; + border-radius: 5px; + cursor: pointer; + padding: 4px; + background-color: transparent; + color: inherit; +} + +.emotion-3:last-of-type { + margin-right: 4px; +} + +.emotion-10 { + border: 0; + border-radius: 5px; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 2px 12px; + font-size: 12px; + font-weight: bold; + border-radius: 3px; +} + +.emotion-10 .emotion-0 { + margin-left: 6px; +} + +.emotion-8 { + display: inline-block; + line-height: 0; + width: 12px; + height: 12px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-8 path:not(.no-fill), +.emotion-8 circle:not(.no-fill), +.emotion-8 polygon:not(.no-fill), +.emotion-8 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-8 path.clipped { + fill: transparent; +} + +.emotion-8 svg { + width: 100%; + height: 100%; +} + +.emotion-1 { + display: inline-block; + line-height: 0; + width: 18px; + height: 18px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-1 path:not(.no-fill), +.emotion-1 circle:not(.no-fill), +.emotion-1 polygon:not(.no-fill), +.emotion-1 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-1 path.clipped { + fill: transparent; +} + +.emotion-1 svg { + width: 100%; + height: 100%; +} + +.emotion-16 { + margin-top: 18px; +} + +.emotion-16:first-of-type { + margin-top: 26px; +} + +.emotion-14 { + display: none; + border-top: 0; + color: inherit; + background-color: #dfdfe3; + padding: 13px; + border-radius: 0 0 5px 5px; +} + +
+
+
+ + 1 list +
+ +
+
+
+ + + +
+ No string +
+ +
+
+
+
+`; + +exports[`ListControl should remove from list when remove button is clicked 1`] = ` + + .emotion-22 { + padding: 0 14px 14px; +} + +.emotion-12 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #dfdfe3; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + margin: 0 -14px; + padding: 13px; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + font-size: 14px; + font-weight: 500; + line-height: 1; +} + +.emotion-3 { + border: 0; + border-radius: 5px; + cursor: pointer; + padding: 4px; + background-color: transparent; + color: inherit; +} + +.emotion-3:last-of-type { + margin-right: 4px; +} + +.emotion-10 { + border: 0; + border-radius: 5px; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 2px 12px; + font-size: 12px; + font-weight: bold; + border-radius: 3px; +} + +.emotion-10 .emotion-0 { + margin-left: 6px; +} + +.emotion-8 { + display: inline-block; + line-height: 0; + width: 12px; + height: 12px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-8 path:not(.no-fill), +.emotion-8 circle:not(.no-fill), +.emotion-8 polygon:not(.no-fill), +.emotion-8 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-8 path.clipped { + fill: transparent; +} + +.emotion-8 svg { + width: 100%; + height: 100%; +} + +.emotion-1 { + display: inline-block; + line-height: 0; + width: 18px; + height: 18px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-1 path:not(.no-fill), +.emotion-1 circle:not(.no-fill), +.emotion-1 polygon:not(.no-fill), +.emotion-1 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-1 path.clipped { + fill: transparent; +} + +.emotion-1 svg { + width: 100%; + height: 100%; +} + +.emotion-16 { + margin-top: 18px; +} + +.emotion-16:first-of-type { + margin-top: 26px; +} + +.emotion-14 { + display: none; + border-top: 0; + color: inherit; + background-color: #dfdfe3; + padding: 13px; + border-radius: 0 0 5px 5px; +} + +
+
+
+ + 2 list +
+ +
+
+
+ + + +
+ item 1 +
+ +
+
+ + + +
+ item 2 +
+ +
+
+
+
+`; + +exports[`ListControl should remove from list when remove button is clicked 2`] = ` + + .emotion-18 { + padding: 0 14px 14px; +} + +.emotion-12 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #dfdfe3; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + margin: 0 -14px; + padding: 13px; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + font-size: 14px; + font-weight: 500; + line-height: 1; +} + +.emotion-3 { + border: 0; + border-radius: 5px; + cursor: pointer; + padding: 4px; + background-color: transparent; + color: inherit; +} + +.emotion-3:last-of-type { + margin-right: 4px; +} + +.emotion-10 { + border: 0; + border-radius: 5px; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 2px 12px; + font-size: 12px; + font-weight: bold; + border-radius: 3px; +} + +.emotion-10 .emotion-0 { + margin-left: 6px; +} + +.emotion-8 { + display: inline-block; + line-height: 0; + width: 12px; + height: 12px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-8 path:not(.no-fill), +.emotion-8 circle:not(.no-fill), +.emotion-8 polygon:not(.no-fill), +.emotion-8 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-8 path.clipped { + fill: transparent; +} + +.emotion-8 svg { + width: 100%; + height: 100%; +} + +.emotion-16 { + margin-top: 18px; + padding-bottom: 0; +} + +.emotion-16:first-of-type { + margin-top: 26px; +} + +.emotion-14 { + display: block; + border-top: 0; + color: inherit; + background-color: #dfdfe3; + padding: 13px; + border-radius: 0 0 5px 5px; +} + +.emotion-1 { + display: inline-block; + line-height: 0; + width: 18px; + height: 18px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-1 path:not(.no-fill), +.emotion-1 circle:not(.no-fill), +.emotion-1 polygon:not(.no-fill), +.emotion-1 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-1 path.clipped { + fill: transparent; +} + +.emotion-1 svg { + width: 100%; + height: 100%; +} + +
+
+
+ + 1 list +
+ +
+
+
+ + + +
+ item 2 +
+ +
+
+
+
+`; + exports[`ListControl should render empty list 1`] = ` `; +exports[`ListControl should render list with fields with collapse = "false" and default minimize_collapsed ("false") 1`] = ` + + .emotion-22 { + padding: 0 14px 14px; +} + +.emotion-12 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #dfdfe3; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + margin: 0 -14px; + padding: 13px; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + font-size: 14px; + font-weight: 500; + line-height: 1; +} + +.emotion-3 { + border: 0; + border-radius: 5px; + cursor: pointer; + padding: 4px; + background-color: transparent; + color: inherit; +} + +.emotion-3:last-of-type { + margin-right: 4px; +} + +.emotion-10 { + border: 0; + border-radius: 5px; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 2px 12px; + font-size: 12px; + font-weight: bold; + border-radius: 3px; +} + +.emotion-10 .emotion-0 { + margin-left: 6px; +} + +.emotion-8 { + display: inline-block; + line-height: 0; + width: 12px; + height: 12px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-8 path:not(.no-fill), +.emotion-8 circle:not(.no-fill), +.emotion-8 polygon:not(.no-fill), +.emotion-8 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-8 path.clipped { + fill: transparent; +} + +.emotion-8 svg { + width: 100%; + height: 100%; +} + +.emotion-1 { + display: inline-block; + line-height: 0; + width: 18px; + height: 18px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-1 path:not(.no-fill), +.emotion-1 circle:not(.no-fill), +.emotion-1 polygon:not(.no-fill), +.emotion-1 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-1 path.clipped { + fill: transparent; +} + +.emotion-1 svg { + width: 100%; + height: 100%; +} + +.emotion-16 { + margin-top: 18px; +} + +.emotion-16:first-of-type { + margin-top: 26px; +} + +.emotion-14 { + display: none; + border-top: 0; + color: inherit; + background-color: #dfdfe3; + padding: 13px; + border-radius: 0 0 5px 5px; +} + +
+
+
+ + 2 list +
+ +
+
+
+ + + +
+ item 1 +
+ +
+
+ + + +
+ item 2 +
+ +
+
+
+
+`; + +exports[`ListControl should render list with fields with collapse = "false" and default minimize_collapsed = "true" 1`] = ` + + .emotion-22 { + padding: 0 14px 14px; +} + +.emotion-12 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #dfdfe3; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + margin: 0 -14px; + padding: 13px; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + font-size: 14px; + font-weight: 500; + line-height: 1; +} + +.emotion-3 { + border: 0; + border-radius: 5px; + cursor: pointer; + padding: 4px; + background-color: transparent; + color: inherit; +} + +.emotion-3:last-of-type { + margin-right: 4px; +} + +.emotion-10 { + border: 0; + border-radius: 5px; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 2px 12px; + font-size: 12px; + font-weight: bold; + border-radius: 3px; +} + +.emotion-10 .emotion-0 { + margin-left: 6px; +} + +.emotion-8 { + display: inline-block; + line-height: 0; + width: 12px; + height: 12px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-8 path:not(.no-fill), +.emotion-8 circle:not(.no-fill), +.emotion-8 polygon:not(.no-fill), +.emotion-8 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-8 path.clipped { + fill: transparent; +} + +.emotion-8 svg { + width: 100%; + height: 100%; +} + +.emotion-1 { + display: inline-block; + line-height: 0; + width: 18px; + height: 18px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-1 path:not(.no-fill), +.emotion-1 circle:not(.no-fill), +.emotion-1 polygon:not(.no-fill), +.emotion-1 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-1 path.clipped { + fill: transparent; +} + +.emotion-1 svg { + width: 100%; + height: 100%; +} + +.emotion-16 { + margin-top: 18px; +} + +.emotion-16:first-of-type { + margin-top: 26px; +} + +.emotion-14 { + display: none; + border-top: 0; + color: inherit; + background-color: #dfdfe3; + padding: 13px; + border-radius: 0 0 5px 5px; +} + +
+
+
+ + 2 list +
+ +
+
+
+ + + +
+ item 1 +
+ +
+
+ + + +
+ item 2 +
+ +
+
+
+
+`; + +exports[`ListControl should render list with fields with default collapse ("true") and minimize_collapsed ("false") 1`] = ` + + .emotion-22 { + padding: 0 14px 14px; +} + +.emotion-12 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #dfdfe3; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + margin: 0 -14px; + padding: 13px; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + font-size: 14px; + font-weight: 500; + line-height: 1; +} + +.emotion-3 { + border: 0; + border-radius: 5px; + cursor: pointer; + padding: 4px; + background-color: transparent; + color: inherit; +} + +.emotion-3:last-of-type { + margin-right: 4px; +} + +.emotion-1 { + display: inline-block; + line-height: 0; + width: 18px; + height: 18px; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); +} + +.emotion-1 path:not(.no-fill), +.emotion-1 circle:not(.no-fill), +.emotion-1 polygon:not(.no-fill), +.emotion-1 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-1 path.clipped { + fill: transparent; +} + +.emotion-1 svg { + width: 100%; + height: 100%; +} + +.emotion-10 { + border: 0; + border-radius: 5px; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 2px 12px; + font-size: 12px; + font-weight: bold; + border-radius: 3px; +} + +.emotion-10 .emotion-0 { + margin-left: 6px; +} + +.emotion-8 { + display: inline-block; + line-height: 0; + width: 12px; + height: 12px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-8 path:not(.no-fill), +.emotion-8 circle:not(.no-fill), +.emotion-8 polygon:not(.no-fill), +.emotion-8 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-8 path.clipped { + fill: transparent; +} + +.emotion-8 svg { + width: 100%; + height: 100%; +} + +.emotion-16 { + margin-top: 18px; + padding-bottom: 0; +} + +.emotion-16:first-of-type { + margin-top: 26px; +} + +.emotion-14 { + display: block; + border-top: 0; + color: inherit; + background-color: #dfdfe3; + padding: 13px; + border-radius: 0 0 5px 5px; +} + +
+
+
+ + 2 list +
+ +
+
+
+ + + +
+ item 1 +
+ +
+
+ + + +
+ item 2 +
+ +
+
+
+
+`; + +exports[`ListControl should render list with fields with default collapse ("true") and minimize_collapsed = "true" 1`] = ` + + .emotion-14 { + padding: 0 14px 14px; +} + +.emotion-12 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #dfdfe3; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + margin: 0 -14px; + padding: 13px; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + font-size: 14px; + font-weight: 500; + line-height: 1; +} + +.emotion-3 { + border: 0; + border-radius: 5px; + cursor: pointer; + padding: 4px; + background-color: transparent; + color: inherit; +} + +.emotion-3:last-of-type { + margin-right: 4px; +} + +.emotion-1 { + display: inline-block; + line-height: 0; + width: 18px; + height: 18px; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); +} + +.emotion-1 path:not(.no-fill), +.emotion-1 circle:not(.no-fill), +.emotion-1 polygon:not(.no-fill), +.emotion-1 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-1 path.clipped { + fill: transparent; +} + +.emotion-1 svg { + width: 100%; + height: 100%; +} + +.emotion-10 { + border: 0; + border-radius: 5px; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 2px 12px; + font-size: 12px; + font-weight: bold; + border-radius: 3px; +} + +.emotion-10 .emotion-0 { + margin-left: 6px; +} + +.emotion-8 { + display: inline-block; + line-height: 0; + width: 12px; + height: 12px; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.emotion-8 path:not(.no-fill), +.emotion-8 circle:not(.no-fill), +.emotion-8 polygon:not(.no-fill), +.emotion-8 rect:not(.no-fill) { + fill: currentColor; +} + +.emotion-8 path.clipped { + fill: transparent; +} + +.emotion-8 svg { + width: 100%; + height: 100%; +} + +
+
+
+ + 2 list +
+ +
+
+
+`; + exports[`ListControl should render list with nested object 1`] = ` .emotion-22 { @@ -212,7 +1868,11 @@ exports[`ListControl should render list with nested object 1`] = ` classname="css-1e1b0lr-StyledListItemTopBar e14bfka81" collapsed="true" data-testid="styled-list-item-top-bar-0" - /> + > + +
@@ -236,7 +1896,11 @@ exports[`ListControl should render list with nested object 1`] = ` classname="css-1e1b0lr-StyledListItemTopBar e14bfka81" collapsed="true" data-testid="styled-list-item-top-bar-1" - /> + > + +
@@ -459,7 +2123,11 @@ exports[`ListControl should render list with nested object with collapse = false classname="css-1e1b0lr-StyledListItemTopBar e14bfka81" collapsed="false" data-testid="styled-list-item-top-bar-0" - /> + > + +
@@ -483,7 +2151,11 @@ exports[`ListControl should render list with nested object with collapse = false classname="css-1e1b0lr-StyledListItemTopBar e14bfka81" collapsed="false" data-testid="styled-list-item-top-bar-1" - /> + > + +
diff --git a/website/content/docs/widgets/list.md b/website/content/docs/widgets/list.md index 8dc64832..e8007ed9 100644 --- a/website/content/docs/widgets/list.md +++ b/website/content/docs/widgets/list.md @@ -13,6 +13,7 @@ The list widget allows you to create a repeatable item in the UI which saves as - `allow_add`: if added and labeled `false`, button to add additional widgets disappears - `collapsed`: if added and labeled `false`, the list widget's content does not collapse by default - `summary`: allows customization of a collapsed list item object in a similar way to a [collection summary](/docs/configuration-options/?#summary) + - `minimize_collapsed`: if added and labeled `true`, the list widget's content will be completely hidden instead of only collapsed if the list widget itself is collapsed - `field`: a single widget field to be repeated - `fields`: a nested list of multiple widget fields to be included in each repeatable iteration - **Example** (`field`/`fields` not specified): @@ -63,3 +64,13 @@ The list widget allows you to create a repeatable item in the UI which saves as - {label: Quote, name: quote, widget: string, default: "Everything is awesome!"} - {label: Author, name: author, widget: string } ``` +- **Example** (`minimize_collapsed` marked `true`): + ```yaml + - label: "Testimonials" + name: "testimonials" + minimize_collapsed: true + widget: "list" + fields: + - {label: Quote, name: quote, widget: string, default: "Everything is awesome!"} + - {label: Author, name: author, widget: string } + ```