From 275eb88dc045740c01e8a5b0c6d4449731ed48f9 Mon Sep 17 00:00:00 2001 From: Pascal Mugnier Date: Fri, 20 Jul 2018 16:11:03 +0200 Subject: [PATCH] SONAR-11014 Create a collapsible component --- server/sonar-docs/gatsby-config.js | 3 +- server/sonar-docs/src/images/close.svg | 1 + server/sonar-docs/src/images/open.svg | 1 + server/sonar-docs/src/templates/page.css | 40 +++++++++ server/sonar-docs/src/templates/page.js | 82 +++++++++++-------- .../src/main/js/apps/documentation/styles.css | 20 +++++ .../components/docs/DocCollapsibleBlock.tsx | 70 ++++++++++++++++ .../js/components/docs/DocMarkdownBlock.tsx | 10 ++- .../__tests__/DocCollapsibleBlock-test.tsx | 48 +++++++++++ .../DocCollapsibleBlock-test.tsx.snap | 48 +++++++++++ 10 files changed, 286 insertions(+), 37 deletions(-) create mode 100644 server/sonar-docs/src/images/close.svg create mode 100644 server/sonar-docs/src/images/open.svg create mode 100644 server/sonar-web/src/main/js/components/docs/DocCollapsibleBlock.tsx create mode 100644 server/sonar-web/src/main/js/components/docs/__tests__/DocCollapsibleBlock-test.tsx create mode 100644 server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocCollapsibleBlock-test.tsx.snap 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 @@ + \ 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 @@ + \ 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( - /\@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(/\

/gi, ''); + collaspables[i].firstChild.addEventListener('click', e => { + e.currentTarget.parentNode.classList.toggle('close'); + e.preventDefault(); + }); } - ); - - if ( - page.headings && - page.headings.length > 0 && - page.html.match(/Table Of Contents<\/h[1-9]>/i) - ) { - htmlWithInclusions = generateTableOfContents(htmlWithInclusions, page.headings); } - return ( -
- -

{page.frontmatter.title}

-
-
- ); -}; + render() { + const page = this.props.data.markdownRemark; + let htmlWithInclusions = cutSonarCloudContent(page.html).replace( + /\@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(/Table Of Contents<\/h[1-9]>/i) + ) { + htmlWithInclusions = generateTableOfContents(htmlWithInclusions, page.headings); + } + + return ( +
+ +

{page.frontmatter.title}

+
+
+ ); + } +} 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) => { + this.setState(state => ({ open: !state.open })); + event.stopPropagation(); + event.preventDefault(); + }; + + renderTitle(children: any) { + return ( + + + {children.props ? children.props.children : children} + + ); + } + + 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).props.children + ); + if (firstChildChildren.length < 2) { + return null; + } + + return ( +
+ {this.renderTitle(firstChildChildren[0])} + {this.state.open && firstChildChildren.slice(1)} +
+ ); + } +} 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 { 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

( function Block(props: React.HtmlHTMLAttributes) { if (props.className) { - return

{props.children}
; + if (props.className.includes('collapse')) { + return {props.children}; + } else { + return
{props.children}
; + } } 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 = ( +
+

Foo

+

Bar

+
+); + +it('should render a collapsible block', () => { + const wrapper = shallow({children}); + 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( + +
foobar
+
+ ); + 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`] = ` + +`; + +exports[`should render a collapsible block 2`] = ` +
+ + + Foo + +

+ Bar +

+
+`; -- 2.39.5