{"action":"created","comment":{"url":"https://api.github.com/repos/edx/paragon/pulls/comments/197224618","pull_request_review_id":130928038,"id":197224618,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5NzIyNDYxOA==","diff_hunk":"@@ -0,0 +1,414 @@\n+import React from 'react';\n+import PropTypes from 'prop-types';\n+import classNames from 'classnames';\n+import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';\n+\n+import Button from '../Button';\n+import Icon from '../Icon';\n+import { ExtraSmall, LargerThanExtraSmall } from '../Responsive';\n+import newId from '../utils/newId';\n+import getTextFromElement from '../utils/getTextFromElement';\n+\n+import styles from './Pagination.scss';\n+\n+class Pagination extends React.Component {\n+  constructor(props) {\n+    super(props);\n+\n+    this.previousButtonRef = null;\n+    this.nextButtonRef = null;\n+\n+    this.pageRefs = {};\n+\n+    this.state = {\n+      currentPage: this.props.currentPage,\n+      previousNextButtonClicked: false,\n+    };\n+  }\n+\n+  componentWillReceiveProps(nextProps) {\n+    if (nextProps.currentPage !== this.props.currentPage) {\n+      this.state.currentPage = nextProps.currentPage;\n+    }\n+  }\n+\n+  componentDidUpdate() {\n+    const { currentPage, previousNextButtonClicked } = this.state;\n+    const currentPageRef = this.pageRefs[currentPage];\n+\n+    if (currentPageRef && !previousNextButtonClicked) {\n+      currentPageRef.focus();\n+    }\n+  }\n+\n+  handlePageSelect(page) {\n+    if (page !== this.state.currentPage) {\n+      this.setState({\n+        currentPage: page,\n+        previousNextButtonClicked: false,\n+      });\n+      this.props.onPageSelect(page);\n+    }\n+  }\n+\n+  handlePreviousNextButtonClick(page) {\n+    const { pageCount } = this.props;\n+\n+    this.setState({\n+      currentPage: page,\n+      previousNextButtonClicked: true,\n+    });\n+\n+    if (page === 1) {\n+      this.nextButtonRef.focus();\n+    } else if (page === pageCount) {\n+      this.previousButtonRef.focus();\n+    }\n+\n+    this.props.onPageSelect(page);\n+  }\n+\n+  renderEllipsisButton() {\n+    return (\n+      <li\n+        className={classNames([styles['page-item'], styles.disabled])}\n+        key={newId('pagination-ellipsis-')}\n+      >\n+        <Button\n+          className={[\n+            styles['page-link'],\n+            styles['ml-0'],\n+            styles['border-0'],\n+          ]}\n+          label=\"...\"\n+          tabIndex=\"-1\"\n+          disabled\n+        />\n+      </li>\n+    );\n+  }\n+\n+  renderPageButton(page) {\n+    const { buttonLabels } = this.props;\n+    const active = page === this.state.currentPage || null;\n+\n+    const ariaLabel = getTextFromElement((\n+      <div>\n+        {buttonLabels.page}\n+        {` ${page}`}\n+        {active &&\n+          <span>\n+            {', '}\n+            {buttonLabels.currentPage}\n+          </span>\n+        }\n+      </div>\n+    ));\n+\n+    return (\n+      <li\n+        className={classNames([\n+          styles['page-item'],\n+          {\n+            [styles.active]: active,\n+          },\n+        ])}\n+        key={page}\n+      >\n+        <Button\n+          className={[styles['page-link']]}\n+          aria-label={ariaLabel}\n+          label={page.toString()}\n+          inputRef={(element) => { this.pageRefs[page] = element; }}\n+          onClick={() => { this.handlePageSelect(page); }}\n+        />\n+      </li>\n+    );\n+  }\n+\n+  renderPageOfCountButton() {\n+    const { currentPage } = this.state;\n+    const { pageCount, buttonLabels } = this.props;\n+\n+    const ariaLabel = getTextFromElement((\n+      <div>\n+        {buttonLabels.page}\n+        {` ${currentPage}, `}\n+        {buttonLabels.currentPage}\n+        {', '}\n+        {buttonLabels.pageOfCount}\n+        {` ${pageCount}`}\n+      </div>\n+    ));\n+\n+    const label = (\n+      <div>\n+        {`${currentPage} `}\n+        {buttonLabels.pageOfCount}\n+        {` ${pageCount}`}\n+      </div>\n+    );\n+\n+    return (\n+      <li\n+        className={classNames([styles['page-item'], styles.disabled])}\n+        key={currentPage}\n+      >\n+        <Button\n+          className={[\n+            styles['page-link'],\n+            styles['mx-2'],\n+            styles['border-0'],\n+          ]}\n+          aria-label={ariaLabel}\n+          label={label}\n+          tabIndex=\"-1\"\n+          disabled\n+        />\n+      </li>\n+    );\n+  }\n+\n+  renderPreviousButton() {\n+    const { buttonLabels } = this.props;\n+    const { currentPage } = this.state;\n+    const isFirstPage = currentPage === 1;\n+    const previousPage = isFirstPage ? null : currentPage - 1;\n+\n+    const ariaLabel = getTextFromElement((\n+      <div>\n+        {buttonLabels.previous}\n+        {previousPage &&\n+          <span>\n+            {', '}\n+            {buttonLabels.page}\n+            {` ${previousPage}`}\n+          </span>\n+        }\n+      </div>\n+    ));\n+\n+    return (\n+      <li\n+        className={classNames(\n+          'page-item',\n+          {\n+            [styles.disabled]: isFirstPage,\n+          },\n+        )}\n+      >\n+        <Button\n+          className={['previous', styles['page-link']]}\n+          aria-label={ariaLabel}\n+          label={\n+            <div>\n+              <Icon\n+                id={newId('pagination-')}\n+                className={[\n+                  FontAwesomeStyles.fa,\n+                  FontAwesomeStyles['fa-chevron-left'],\n+                  styles['mr-2'],\n+                ]}\n+              />\n+              {buttonLabels.previous}\n+            </div>\n+          }\n+          tabIndex={isFirstPage ? '-1' : undefined}\n+          onClick={() => { this.handlePreviousNextButtonClick(previousPage); }}\n+          inputRef={(element) => { this.previousButtonRef = element; }}\n+          disabled={isFirstPage}\n+        />\n+      </li>\n+    );\n+  }\n+\n+  renderNextButton() {\n+    const { buttonLabels, pageCount } = this.props;\n+    const { currentPage } = this.state;\n+    const isLastPage = currentPage === pageCount;\n+    const nextPage = isLastPage ? null : currentPage + 1;\n+\n+    const ariaLabel = getTextFromElement((\n+      <div>\n+        {buttonLabels.next}\n+        {nextPage &&\n+          <span>\n+            {', '}\n+            {buttonLabels.page}\n+            {` ${nextPage}`}\n+          </span>\n+        }\n+      </div>\n+    ));\n+\n+    return (\n+      <li\n+        className={classNames(\n+          styles['page-item'],\n+          {\n+            disabled: isLastPage,\n+          },\n+        )}\n+      >\n+        <Button\n+          className={['next', styles['page-link']]}\n+          aria-label={ariaLabel}\n+          label={\n+            <div className={classNames(styles['btn-icon'])}>\n+              {buttonLabels.next}\n+              <Icon\n+                id={newId('pagination-')}\n+                className={[\n+                  FontAwesomeStyles.fa,\n+                  FontAwesomeStyles['fa-chevron-right'],\n+                  styles['ml-2'],\n+                ]}\n+              />\n+            </div>\n+          }\n+          tabIndex={isLastPage ? '-1' : undefined}\n+          onClick={() => { this.handlePreviousNextButtonClick(nextPage); }}\n+          inputRef={(element) => { this.nextButtonRef = element; }}\n+          disabled={isLastPage}\n+        />\n+      </li>\n+    );\n+  }\n+\n+  renderScreenReaderSection() {\n+    const { currentPage } = this.state;\n+    const { buttonLabels, pageCount } = this.props;\n+    return (\n+      <div\n+        className=\"sr-only\"\n+        aria-live=\"polite\"\n+        aria-relevant=\"text\"\n+        aria-atomic\n+      >\n+        {\n+          getTextFromElement((\n+            <div>\n+              {buttonLabels.page}\n+              {` ${currentPage}, `}\n+              {buttonLabels.currentPage}\n+              {', '}\n+              {buttonLabels.pageOfCount}\n+              {` ${pageCount}`}\n+            </div>\n+          ))\n+        }\n+      </div>\n+    );\n+  }\n+\n+  /*\n+   * Uses recursion to generate an array of the buttons to display (i.e., page/ellipsis\n+   * buttons) given the currently selected page, the max number of buttons to display, and\n+   * the total number of pages.\n+   */\n+  renderPageButtons(range) {\n+    const { currentPage } = this.state;\n+    const { pageCount, maxPagesDisplayed } = this.props;\n+\n+    const pageRange = range || [...Array(pageCount).keys()].map(page => page + 1);\n+    const currentIndex = pageRange.indexOf(currentPage);\n+    const middleIndex = parseInt(pageRange.length / 2, 10) - 1;\n+    const lastIndex = pageRange.length - 1;\n+    const hasLeftEllipsis = pageRange[1] - pageRange[0] > 1;\n+    const hasRightEllipsis = pageRange[lastIndex] - pageRange[lastIndex - 1] > 1;\n+\n+    let ellipsisCount = 0;\n+\n+    ellipsisCount += hasLeftEllipsis;\n+    ellipsisCount += hasRightEllipsis;\n+\n+    if (pageRange.length <= maxPagesDisplayed - ellipsisCount) {\n+      return pageRange.map((page) => {\n+        const pageButtons = [this.renderPageButton(page)];\n+\n+        if (page === 1 && hasLeftEllipsis) {\n+          pageButtons.push(this.renderEllipsisButton());\n+        } else if (page === pageCount && hasRightEllipsis) {\n+          pageButtons.unshift(this.renderEllipsisButton());\n+        }\n+\n+        return pageButtons;\n+      });\n+    }\n+\n+    if (currentIndex > middleIndex) {\n+      const first = pageRange.shift();\n+      pageRange.shift();\n+      pageRange.unshift(first);\n+    } else {\n+      const last = pageRange.pop();\n+      pageRange.pop();\n+      pageRange.push(last);\n+    }\n+\n+    return this.renderPageButtons(pageRange);\n+  }\n+\n+  render() {\n+    return (\n+      <nav\n+        aria-label={this.props.paginationLabel}\n+        className={this.props.className}\n+      >\n+        {this.renderScreenReaderSection()}\n+        <ul className={styles.pagination}>\n+          {this.renderPreviousButton()}\n+          <ExtraSmall>\n+            {this.renderPageOfCountButton()}\n+          </ExtraSmall>\n+          <LargerThanExtraSmall>\n+            {this.renderPageButtons()}\n+          </LargerThanExtraSmall>\n+          {this.renderNextButton()}\n+        </ul>\n+      </nav>\n+    );\n+  }\n+}\n+\n+const maxPagesDisplayedValidator = (props, propName, componentName) => {","path":"src/Pagination/index.jsx","position":374,"original_position":374,"commit_id":"fd5a0edba95a75da0d3b920d5c6c6b268f9dd287","original_commit_id":"16095d8a81b9836f9a2fa7131a016743ac167ec8","user":{"login":"adamstankiewicz","id":2828721,"node_id":"MDQ6VXNlcjI4Mjg3MjE=","avatar_url":"https://avatars0.githubusercontent.com/u/2828721?v=4","gravatar_id":"","url":"https://api.github.com/users/adamstankiewicz","html_url":"https://github.com/adamstankiewicz","followers_url":"https://api.github.com/users/adamstankiewicz/followers","following_url":"https://api.github.com/users/adamstankiewicz/following{/other_user}","gists_url":"https://api.github.com/users/adamstankiewicz/gists{/gist_id}","starred_url":"https://api.github.com/users/adamstankiewicz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adamstankiewicz/subscriptions","organizations_url":"https://api.github.com/users/adamstankiewicz/orgs","repos_url":"https://api.github.com/users/adamstankiewicz/repos","events_url":"https://api.github.com/users/adamstankiewicz/events{/privacy}","received_events_url":"https://api.github.com/users/adamstankiewicz/received_events","type":"User","site_admin":false},"body":"I noticed you were using the `airbnb-proptypes` in the ListBox PR, and meant to revisit this PR to start using the `between` validation now that we use it. I think I was just trying to avoid installing `airbnb-proptypes` for this one use case. But makes perfect sense to use it now that its used elsewhere!","created_at":"2018-06-21T17:55:14Z","updated_at":"2018-06-21T17:55:14Z","html_url":"https://github.com/edx/paragon/pull/283#discussion_r197224618","pull_request_url":"https://api.github.com/repos/edx/paragon/pulls/283","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/edx/paragon/pulls/comments/197224618"},"html":{"href":"https://github.com/edx/paragon/pull/283#discussion_r197224618"},"pull_request":{"href":"https://api.github.com/repos/edx/paragon/pulls/283"}},"in_reply_to_id":196538559},"pull_request":{"url":"https://api.github.com/repos/edx/paragon/pulls/283","id":195501320,"node_id":"MDExOlB1bGxSZXF1ZXN0MTk1NTAxMzIw","html_url":"https://github.com/edx/paragon/pull/283","diff_url":"https://github.com/edx/paragon/pull/283.diff","patch_url":"https://github.com/edx/paragon/pull/283.patch","issue_url":"https://api.github.com/repos/edx/paragon/issues/283","number":283,"state":"open","locked":false,"title":"feat(pagination): add Pagination and Responsive components","user":{"login":"adamstankiewicz","id":2828721,"node_id":"MDQ6VXNlcjI4Mjg3MjE=","avatar_url":"https://avatars0.githubusercontent.com/u/2828721?v=4","gravatar_id":"","url":"https://api.github.com/users/adamstankiewicz","html_url":"https://github.com/adamstankiewicz","followers_url":"https://api.github.com/users/adamstankiewicz/followers","following_url":"https://api.github.com/users/adamstankiewicz/following{/other_user}","gists_url":"https://api.github.com/users/adamstankiewicz/gists{/gist_id}","starred_url":"https://api.github.com/users/adamstankiewicz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adamstankiewicz/subscriptions","organizations_url":"https://api.github.com/users/adamstankiewicz/orgs","repos_url":"https://api.github.com/users/adamstankiewicz/repos","events_url":"https://api.github.com/users/adamstankiewicz/events{/privacy}","received_events_url":"https://api.github.com/users/adamstankiewicz/received_events","type":"User","site_admin":false},"body":"This PR adds an accessible and responsive `Pagination` component ([view demo](http://stale-cause.surge.sh/?selectedKind=Pagination)):\r\n\r\n#### Desktop\r\n![image](https://user-images.githubusercontent.com/2828721/41522823-b2a0501e-72a5-11e8-95a1-633259ab5fdd.png)\r\n\r\n#### Mobile\r\n![image](https://user-images.githubusercontent.com/2828721/41522829-b8cb264e-72a5-11e8-80c5-e85e00fb151a.png)\r\n\r\nTo make the `Pagination` component responsive, I am using [`react-responsive`](https://github.com/contra/react-responsive) to create several wrapper components that render its children depending on window size, according to the [Bootstrap-defined breakpoints](https://getbootstrap.com/docs/4.0/layout/overview/#responsive-breakpoints) (e.g., `<ExtraSmall>` will only render its children for window sizes less than `576px`).\r\n\r\nAnother thing to note is that the `aria-label` attribute on buttons is dynamically created to be more understandable and descriptive for screen readers. For example, when tabbing between pages, the `aria-label` for a currently selected page would be `Page 1, Current Page` whereas the `aria-label` for a page not currently selected would read `Page 2`.\r\n\r\nUnfortunately, a simple string is not sufficient for these dynamic `aria-label`s due to i18n support (e.g., using an element for the labels as in the case of the [`WrappedMessage` component](https://github.com/edx/studio-frontend/blob/master/src/utils/i18n/formattedMessageWrapper.jsx) in studio-frontend). \r\n\r\nBy default, when trying to output an element as a string, it will return as an object and not a human-readable string of the element's text. As a result, I introduce a utility function `getTextFromElement` which accepts an element and returns the raw text of the element through the use of [`renderToStaticMarkup`](https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup) and [`sanitize-html`](https://github.com/punkave/sanitize-html).","created_at":"2018-06-18T11:55:53Z","updated_at":"2018-06-21T17:55:14Z","closed_at":null,"merged_at":null,"merge_commit_sha":"889ae3b6eb914677904aa4bb75b19f86e73dc91c","assignee":null,"assignees":[],"requested_reviewers":[{"login":"arizzitano","id":491289,"node_id":"MDQ6VXNlcjQ5MTI4OQ==","avatar_url":"https://avatars1.githubusercontent.com/u/491289?v=4","gravatar_id":"","url":"https://api.github.com/users/arizzitano","html_url":"https://github.com/arizzitano","followers_url":"https://api.github.com/users/arizzitano/followers","following_url":"https://api.github.com/users/arizzitano/following{/other_user}","gists_url":"https://api.github.com/users/arizzitano/gists{/gist_id}","starred_url":"https://api.github.com/users/arizzitano/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arizzitano/subscriptions","organizations_url":"https://api.github.com/users/arizzitano/orgs","repos_url":"https://api.github.com/users/arizzitano/repos","events_url":"https://api.github.com/users/arizzitano/events{/privacy}","received_events_url":"https://api.github.com/users/arizzitano/received_events","type":"User","site_admin":false},{"login":"AlasdairSwan","id":2052185,"node_id":"MDQ6VXNlcjIwNTIxODU=","avatar_url":"https://avatars1.githubusercontent.com/u/2052185?v=4","gravatar_id":"","url":"https://api.github.com/users/AlasdairSwan","html_url":"https://github.com/AlasdairSwan","followers_url":"https://api.github.com/users/AlasdairSwan/followers","following_url":"https://api.github.com/users/AlasdairSwan/following{/other_user}","gists_url":"https://api.github.com/users/AlasdairSwan/gists{/gist_id}","starred_url":"https://api.github.com/users/AlasdairSwan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlasdairSwan/subscriptions","organizations_url":"https://api.github.com/users/AlasdairSwan/orgs","repos_url":"https://api.github.com/users/AlasdairSwan/repos","events_url":"https://api.github.com/users/AlasdairSwan/events{/privacy}","received_events_url":"https://api.github.com/users/AlasdairSwan/received_events","type":"User","site_admin":false},{"login":"fysheets","id":9357438,"node_id":"MDQ6VXNlcjkzNTc0Mzg=","avatar_url":"https://avatars3.githubusercontent.com/u/9357438?v=4","gravatar_id":"","url":"https://api.github.com/users/fysheets","html_url":"https://github.com/fysheets","followers_url":"https://api.github.com/users/fysheets/followers","following_url":"https://api.github.com/users/fysheets/following{/other_user}","gists_url":"https://api.github.com/users/fysheets/gists{/gist_id}","starred_url":"https://api.github.com/users/fysheets/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fysheets/subscriptions","organizations_url":"https://api.github.com/users/fysheets/orgs","repos_url":"https://api.github.com/users/fysheets/repos","events_url":"https://api.github.com/users/fysheets/events{/privacy}","received_events_url":"https://api.github.com/users/fysheets/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/edx/paragon/pulls/283/commits","review_comments_url":"https://api.github.com/repos/edx/paragon/pulls/283/comments","review_comment_url":"https://api.github.com/repos/edx/paragon/pulls/comments{/number}","comments_url":"https://api.github.com/repos/edx/paragon/issues/283/comments","statuses_url":"https://api.github.com/repos/edx/paragon/statuses/fd5a0edba95a75da0d3b920d5c6c6b268f9dd287","head":{"label":"edx:astankiewicz/pagination","ref":"astankiewicz/pagination","sha":"fd5a0edba95a75da0d3b920d5c6c6b268f9dd287","user":{"login":"edx","id":3179841,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxNzk4NDE=","avatar_url":"https://avatars3.githubusercontent.com/u/3179841?v=4","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":89630641,"node_id":"MDEwOlJlcG9zaXRvcnk4OTYzMDY0MQ==","name":"paragon","full_name":"edx/paragon","owner":{"login":"edx","id":3179841,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxNzk4NDE=","avatar_url":"https://avatars3.githubusercontent.com/u/3179841?v=4","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/paragon","description":"💎 Accessible components done right.","fork":false,"url":"https://api.github.com/repos/edx/paragon","forks_url":"https://api.github.com/repos/edx/paragon/forks","keys_url":"https://api.github.com/repos/edx/paragon/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/paragon/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/paragon/teams","hooks_url":"https://api.github.com/repos/edx/paragon/hooks","issue_events_url":"https://api.github.com/repos/edx/paragon/issues/events{/number}","events_url":"https://api.github.com/repos/edx/paragon/events","assignees_url":"https://api.github.com/repos/edx/paragon/assignees{/user}","branches_url":"https://api.github.com/repos/edx/paragon/branches{/branch}","tags_url":"https://api.github.com/repos/edx/paragon/tags","blobs_url":"https://api.github.com/repos/edx/paragon/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/paragon/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/paragon/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/paragon/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/paragon/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/paragon/languages","stargazers_url":"https://api.github.com/repos/edx/paragon/stargazers","contributors_url":"https://api.github.com/repos/edx/paragon/contributors","subscribers_url":"https://api.github.com/repos/edx/paragon/subscribers","subscription_url":"https://api.github.com/repos/edx/paragon/subscription","commits_url":"https://api.github.com/repos/edx/paragon/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/paragon/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/paragon/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/paragon/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/paragon/contents/{+path}","compare_url":"https://api.github.com/repos/edx/paragon/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/paragon/merges","archive_url":"https://api.github.com/repos/edx/paragon/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/paragon/downloads","issues_url":"https://api.github.com/repos/edx/paragon/issues{/number}","pulls_url":"https://api.github.com/repos/edx/paragon/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/paragon/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/paragon/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/paragon/labels{/name}","releases_url":"https://api.github.com/repos/edx/paragon/releases{/id}","deployments_url":"https://api.github.com/repos/edx/paragon/deployments","created_at":"2017-04-27T18:57:22Z","updated_at":"2018-06-20T15:33:19Z","pushed_at":"2018-06-21T13:19:16Z","git_url":"git://github.com/edx/paragon.git","ssh_url":"git@github.com:edx/paragon.git","clone_url":"https://github.com/edx/paragon.git","svn_url":"https://github.com/edx/paragon","homepage":"https://edx.github.io/paragon","size":3662,"stargazers_count":18,"watchers_count":18,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":52,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":2,"open_issues":52,"watchers":18,"default_branch":"master"}},"base":{"label":"edx:master","ref":"master","sha":"f7998b28778914b98a54e8833c0e5af9a42efd58","user":{"login":"edx","id":3179841,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxNzk4NDE=","avatar_url":"https://avatars3.githubusercontent.com/u/3179841?v=4","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":89630641,"node_id":"MDEwOlJlcG9zaXRvcnk4OTYzMDY0MQ==","name":"paragon","full_name":"edx/paragon","owner":{"login":"edx","id":3179841,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxNzk4NDE=","avatar_url":"https://avatars3.githubusercontent.com/u/3179841?v=4","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/paragon","description":"💎 Accessible components done right.","fork":false,"url":"https://api.github.com/repos/edx/paragon","forks_url":"https://api.github.com/repos/edx/paragon/forks","keys_url":"https://api.github.com/repos/edx/paragon/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/paragon/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/paragon/teams","hooks_url":"https://api.github.com/repos/edx/paragon/hooks","issue_events_url":"https://api.github.com/repos/edx/paragon/issues/events{/number}","events_url":"https://api.github.com/repos/edx/paragon/events","assignees_url":"https://api.github.com/repos/edx/paragon/assignees{/user}","branches_url":"https://api.github.com/repos/edx/paragon/branches{/branch}","tags_url":"https://api.github.com/repos/edx/paragon/tags","blobs_url":"https://api.github.com/repos/edx/paragon/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/paragon/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/paragon/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/paragon/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/paragon/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/paragon/languages","stargazers_url":"https://api.github.com/repos/edx/paragon/stargazers","contributors_url":"https://api.github.com/repos/edx/paragon/contributors","subscribers_url":"https://api.github.com/repos/edx/paragon/subscribers","subscription_url":"https://api.github.com/repos/edx/paragon/subscription","commits_url":"https://api.github.com/repos/edx/paragon/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/paragon/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/paragon/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/paragon/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/paragon/contents/{+path}","compare_url":"https://api.github.com/repos/edx/paragon/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/paragon/merges","archive_url":"https://api.github.com/repos/edx/paragon/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/paragon/downloads","issues_url":"https://api.github.com/repos/edx/paragon/issues{/number}","pulls_url":"https://api.github.com/repos/edx/paragon/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/paragon/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/paragon/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/paragon/labels{/name}","releases_url":"https://api.github.com/repos/edx/paragon/releases{/id}","deployments_url":"https://api.github.com/repos/edx/paragon/deployments","created_at":"2017-04-27T18:57:22Z","updated_at":"2018-06-20T15:33:19Z","pushed_at":"2018-06-21T13:19:16Z","git_url":"git://github.com/edx/paragon.git","ssh_url":"git@github.com:edx/paragon.git","clone_url":"https://github.com/edx/paragon.git","svn_url":"https://github.com/edx/paragon","homepage":"https://edx.github.io/paragon","size":3662,"stargazers_count":18,"watchers_count":18,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":52,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":2,"open_issues":52,"watchers":18,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/edx/paragon/pulls/283"},"html":{"href":"https://github.com/edx/paragon/pull/283"},"issue":{"href":"https://api.github.com/repos/edx/paragon/issues/283"},"comments":{"href":"https://api.github.com/repos/edx/paragon/issues/283/comments"},"review_comments":{"href":"https://api.github.com/repos/edx/paragon/pulls/283/comments"},"review_comment":{"href":"https://api.github.com/repos/edx/paragon/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/edx/paragon/pulls/283/commits"},"statuses":{"href":"https://api.github.com/repos/edx/paragon/statuses/fd5a0edba95a75da0d3b920d5c6c6b268f9dd287"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"adamstankiewicz"}}