diff options
author | Pascal Mugnier <pascal.mugnier@sonarsource.com> | 2018-07-20 16:11:03 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-07-25 20:21:21 +0200 |
commit | 275eb88dc045740c01e8a5b0c6d4449731ed48f9 (patch) | |
tree | 3e12736cac326fe13e8c3026503c351ceb5c64c8 /server | |
parent | c76da4346704fdc8a2ce5ecedb1e0461186ba222 (diff) | |
download | sonarqube-275eb88dc045740c01e8a5b0c6d4449731ed48f9.tar.gz sonarqube-275eb88dc045740c01e8a5b0c6d4449731ed48f9.zip |
SONAR-11014 Create a collapsible component
Diffstat (limited to 'server')
10 files changed, 286 insertions, 37 deletions
diff --git a/server/sonar-docs/gatsby-config.js b/server/sonar-docs/gatsby-config.js index 19d540c5c43..831aa76758d 100644 --- a/server/sonar-docs/gatsby-config.js +++ b/server/sonar-docs/gatsby-config.js @@ -42,7 +42,8 @@ module.exports = { danger: 'alert alert-danger', warning: 'alert alert-warning', info: 'alert alert-info', - success: 'alert alert-success' + success: 'alert alert-success', + collapse: 'collapse' } } } diff --git a/server/sonar-docs/src/images/close.svg b/server/sonar-docs/src/images/close.svg new file mode 100644 index 00000000000..10b0b0a44f9 --- /dev/null +++ b/server/sonar-docs/src/images/close.svg @@ -0,0 +1 @@ +<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M11.596 8.28l-4.604 4.602a.382.382 0 0 1-.279.118.382.382 0 0 1-.279-.118l-1.03-1.03a.382.382 0 0 1-.117-.278c0-.108.04-.201.117-.28L8.7 8 5.404 4.706a.382.382 0 0 1-.117-.28c0-.108.04-.2.117-.279l1.03-1.03A.382.382 0 0 1 6.714 3c.107 0 .2.04.278.118l4.604 4.603a.382.382 0 0 1 .117.279c0 .108-.04.201-.117.28z" fill="#236a97" /></svg>
\ No newline at end of file diff --git a/server/sonar-docs/src/images/open.svg b/server/sonar-docs/src/images/open.svg new file mode 100644 index 00000000000..b5d7e4c6d26 --- /dev/null +++ b/server/sonar-docs/src/images/open.svg @@ -0,0 +1 @@ +<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M7.72 11.596L3.119 6.992A.382.382 0 0 1 3 6.713c0-.108.04-.2.118-.279l1.03-1.03a.382.382 0 0 1 .278-.117c.108 0 .201.04.28.117L8 8.7l3.294-3.295a.382.382 0 0 1 .28-.117c.108 0 .2.04.279.117l1.03 1.03a.382.382 0 0 1 .117.28c0 .107-.04.2-.118.278L8.28 11.596a.382.382 0 0 1-.279.117.382.382 0 0 1-.28-.117z" fill="#236a97" /></svg>
\ No newline at end of file diff --git a/server/sonar-docs/src/templates/page.css b/server/sonar-docs/src/templates/page.css index cf8ff3dd405..1e6c4dd8de5 100644 --- a/server/sonar-docs/src/templates/page.css +++ b/server/sonar-docs/src/templates/page.css @@ -32,3 +32,43 @@ background-color: #dff0d8; color: #3c763d; } + +.collapse { + border: 1px solid #e6e6e6; + border-radius: 2px; + background-color: #f3f3f3; + padding: 8px; + margin: 0 -1em 1.5rem; +} + +.collapse > a:first-child { + background: url(../images/open.svg) no-repeat 0 50%; + padding-left: 20px; + display: block; + color: #236a97; + display: block; + cursor: pointer; + margin-bottom: 0.5rem; + font-size: 16px; + text-decoration: none; +} + +.collapse.close > a:first-child { + background: url(../images/close.svg) no-repeat 0 50%; +} + +.collapse.close > * { + display: none; +} + +.collapse.close > a:first-child { + margin: 0; +} + +.collapse *:last-child { + margin-bottom: 0; +} + +.collapse .alert { + margin: 0 0.5em 1.5rem; +} diff --git a/server/sonar-docs/src/templates/page.js b/server/sonar-docs/src/templates/page.js index 45040ee4807..2c04417fe55 100644 --- a/server/sonar-docs/src/templates/page.js +++ b/server/sonar-docs/src/templates/page.js @@ -21,43 +21,57 @@ import React from 'react'; import Helmet from 'react-helmet'; import './page.css'; -export default ({ data }) => { - const page = data.markdownRemark; - let htmlWithInclusions = cutSonarCloudContent(page.html).replace( - /\<p\>@include (.*)\<\/p\>/, - (_, path) => { - const chunk = data.allMarkdownRemark.edges.find( - edge => edge.node.fields && edge.node.fields.slug === path - ); - return chunk ? chunk.node.html : ''; +export default class Page extends React.PureComponent { + componentDidMount() { + const collaspables = document.getElementsByClassName('collapse'); + for (let i = 0; i < collaspables.length; i++) { + collaspables[i].classList.add('close'); + collaspables[i].firstChild.outerHTML = collaspables[i].firstChild.outerHTML + .replace(/\<h2/gi, '<a href="#"') + .replace(/\<\/h2\>/gi, '</a>'); + collaspables[i].firstChild.addEventListener('click', e => { + e.currentTarget.parentNode.classList.toggle('close'); + e.preventDefault(); + }); } - ); - - if ( - page.headings && - page.headings.length > 0 && - page.html.match(/<h[1-9]>Table Of Contents<\/h[1-9]>/i) - ) { - htmlWithInclusions = generateTableOfContents(htmlWithInclusions, page.headings); } - return ( - <div css={{ paddingTop: 24, paddingBottom: 24 }}> - <Helmet title={page.frontmatter.title} /> - <h1>{page.frontmatter.title}</h1> - <div - css={{ - '& img[src$=".svg"]': { - position: 'relative', - top: '-2px', - verticalAlign: 'text-bottom' - } - }} - dangerouslySetInnerHTML={{ __html: htmlWithInclusions }} - /> - </div> - ); -}; + render() { + const page = this.props.data.markdownRemark; + let htmlWithInclusions = cutSonarCloudContent(page.html).replace( + /\<p\>@include (.*)\<\/p\>/, + (_, path) => { + const chunk = data.allMarkdownRemark.edges.find(edge => edge.node.fields.slug === path); + return chunk ? chunk.node.html : ''; + } + ); + + if ( + page.headings && + page.headings.length > 0 && + page.html.match(/<h[1-9]>Table Of Contents<\/h[1-9]>/i) + ) { + htmlWithInclusions = generateTableOfContents(htmlWithInclusions, page.headings); + } + + return ( + <div css={{ paddingTop: 24, paddingBottom: 24 }}> + <Helmet title={page.frontmatter.title} /> + <h1>{page.frontmatter.title}</h1> + <div + css={{ + '& img[src$=".svg"]': { + position: 'relative', + top: '-2px', + verticalAlign: 'text-bottom' + } + }} + dangerouslySetInnerHTML={{ __html: htmlWithInclusions }} + /> + </div> + ); + } +} export const query = graphql` query PageQuery($slug: String!) { diff --git a/server/sonar-web/src/main/js/apps/documentation/styles.css b/server/sonar-web/src/main/js/apps/documentation/styles.css index 04941089c06..5eb00833df4 100644 --- a/server/sonar-web/src/main/js/apps/documentation/styles.css +++ b/server/sonar-web/src/main/js/apps/documentation/styles.css @@ -81,3 +81,23 @@ .documentation-content.markdown .alert p { margin: 0; } + +.documentation-content.markdown .collapse-container { + border: 1px solid var(--barBorderColor); + border-radius: 2px; + background-color: var(--barBackgroundColor); + padding: 8px; + margin: 0.8em 0 2em; +} + +.documentation-content.markdown .collapse-container > a:first-child { + display: block; +} + +.documentation-content.markdown .collapse-container > a:first-child:focus { + color: var(--darkBlue); +} + +.documentation-content.markdown .collapse-container *:last-child { + margin-bottom: 0; +} diff --git a/server/sonar-web/src/main/js/components/docs/DocCollapsibleBlock.tsx b/server/sonar-web/src/main/js/components/docs/DocCollapsibleBlock.tsx new file mode 100644 index 00000000000..47b8c96183d --- /dev/null +++ b/server/sonar-web/src/main/js/components/docs/DocCollapsibleBlock.tsx @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import OpenCloseIcon from '../icons-components/OpenCloseIcon'; + +interface State { + open: boolean; +} + +export default class DocCollapsibleBlock extends React.PureComponent<{}, State> { + state = { open: false }; + + handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => { + this.setState(state => ({ open: !state.open })); + event.stopPropagation(); + event.preventDefault(); + }; + + renderTitle(children: any) { + return ( + <a + aria-expanded={String(this.state.open)} + aria-haspopup={true} + className="link-no-underline" + href="#" + onClick={this.handleClick}> + <OpenCloseIcon className="vertical-middle little-spacer-right" open={this.state.open} /> + {children.props ? children.props.children : children} + </a> + ); + } + + render() { + const childrenAsArray = React.Children.toArray(this.props.children); + if (childrenAsArray.length < 1) { + return null; + } + + const firstChildChildren = React.Children.toArray( + (childrenAsArray[0] as React.ReactElement<any>).props.children + ); + if (firstChildChildren.length < 2) { + return null; + } + + return ( + <div className="collapse-container"> + {this.renderTitle(firstChildChildren[0])} + {this.state.open && firstChildChildren.slice(1)} + </div> + ); + } +} diff --git a/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx b/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx index 72978acb947..bb033ac2999 100644 --- a/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx +++ b/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx @@ -26,6 +26,7 @@ import remarkCustomBlocks from 'remark-custom-blocks'; import DocLink from './DocLink'; import DocImg from './DocImg'; import DocTooltipLink from './DocTooltipLink'; +import DocCollapsibleBlock from './DocCollapsibleBlock'; import { separateFrontMatter, filterContent } from '../../helpers/markdown'; import { scrollToElement } from '../../helpers/scrolling'; @@ -63,7 +64,8 @@ export default class DocMarkdownBlock extends React.PureComponent<Props> { danger: { classes: 'alert alert-danger' }, warning: { classes: 'alert alert-warning' }, info: { classes: 'alert alert-info' }, - success: { classes: 'alert alert-success' } + success: { classes: 'alert alert-success' }, + collapse: { classes: 'collapse' } }) .use(reactRenderer, { remarkReactComponents: { @@ -96,7 +98,11 @@ function withChildProps<P>( function Block(props: React.HtmlHTMLAttributes<HTMLDivElement>) { if (props.className) { - return <div className={classNames('cut-margins', props.className)}>{props.children}</div>; + if (props.className.includes('collapse')) { + return <DocCollapsibleBlock>{props.children}</DocCollapsibleBlock>; + } else { + return <div className={classNames('cut-margins', props.className)}>{props.children}</div>; + } } else { return props.children; } diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocCollapsibleBlock-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocCollapsibleBlock-test.tsx new file mode 100644 index 00000000000..c18666d7a2f --- /dev/null +++ b/server/sonar-web/src/main/js/components/docs/__tests__/DocCollapsibleBlock-test.tsx @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import DocCollapsibleBlock from '../DocCollapsibleBlock'; +import { click } from '../../../helpers/testUtils'; + +const children = ( + <div> + <h2>Foo</h2> + <p>Bar</p> + </div> +); + +it('should render a collapsible block', () => { + const wrapper = shallow(<DocCollapsibleBlock>{children}</DocCollapsibleBlock>); + expect(wrapper).toMatchSnapshot(); + + click(wrapper.find('a')); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should not render if not at least 2 children', () => { + const wrapper = shallow( + <DocCollapsibleBlock> + <div>foobar</div> + </DocCollapsibleBlock> + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocCollapsibleBlock-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocCollapsibleBlock-test.tsx.snap new file mode 100644 index 00000000000..763267113d6 --- /dev/null +++ b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocCollapsibleBlock-test.tsx.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should not render if not at least 2 children 1`] = `""`; + +exports[`should render a collapsible block 1`] = ` +<div + className="collapse-container" +> + <a + aria-expanded="false" + aria-haspopup={true} + className="link-no-underline" + href="#" + onClick={[Function]} + > + <OpenCloseIcon + className="vertical-middle little-spacer-right" + open={false} + /> + Foo + </a> +</div> +`; + +exports[`should render a collapsible block 2`] = ` +<div + className="collapse-container" +> + <a + aria-expanded="true" + aria-haspopup={true} + className="link-no-underline" + href="#" + onClick={[Function]} + > + <OpenCloseIcon + className="vertical-middle little-spacer-right" + open={true} + /> + Foo + </a> + <p + key=".1" + > + Bar + </p> +</div> +`; |