diff --git a/.github/ISSUE_TEMPLATE/4.docs-feedback.yml b/.github/ISSUE_TEMPLATE/4.docs-feedback.yml new file mode 100644 index 00000000000000..88fac0f863eb2e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4.docs-feedback.yml @@ -0,0 +1,52 @@ +name: Docs feedback +description: Improve documentation about MUI Core. +labels: ['status: needs triage', 'support: docs-feedback'] +title: '[docs] ' +body: + - type: markdown + attributes: + value: | + Please provide a searchable summary of the issue in the title above ⬆️. + + Thanks for contributing by creating an issue! ❤️ + - type: checkboxes + attributes: + label: Duplicates + description: Please [search the history](https://github.com/mui/material-ui/issues) to see if an issue already exists for the same problem. + options: + - label: I have searched the existing issues + required: true + + - type: input + id: page-url + attributes: + label: Related page + description: Which page of the documentation is this about? + placeholder: https://mui.com/material-ui/react-badge/ + validations: + required: true + + - type: dropdown + attributes: + label: Kind of issue + description: What kind of problem are you facing? + options: + - Unclear explanations + - Missing information + - Broken demonstration + - Other + validations: + required: true + + - type: textarea + attributes: + label: Issue description + description: | + Let us know what went wrong when you were using this documentation and what we could do to improve it. + value: | + I was looking for ... and it appears that ... + + - type: textarea + attributes: + label: Context 🔦 + description: What are you trying to accomplish? What brought you to this page? Your context can help us to come up with solutions that benefit the community as a whole. diff --git a/docs/data/material/guides/pickers-migration/pickers-migration.md b/docs/data/material/guides/pickers-migration/pickers-migration.md index 93f2928fe7e0d6..a139b0fca05b9e 100644 --- a/docs/data/material/guides/pickers-migration/pickers-migration.md +++ b/docs/data/material/guides/pickers-migration/pickers-migration.md @@ -3,7 +3,7 @@

@material-ui/pickers was moved to the @mui/lab.

:::info -**Stable package available**: The pickers are not available in `@mui/lab`after `v5.0.0-alpha.89`. +**Stable package available**: The pickers are not available in `@mui/lab` after `v5.0.0-alpha.89`. They have been moved from `@mui/lab` to the MUI X packages `@mui/x-date-pickers` and `@mui/x-date-pickers-pro`. To migrate from `@mui/lab` to `@mui/x-date-pickers` you can follow the dedicated [migration guide](/x/react-date-pickers/migration-lab/). ::: diff --git a/docs/next.config.js b/docs/next.config.js index de65ad6f342b8b..aa41d36d65f252 100644 --- a/docs/next.config.js +++ b/docs/next.config.js @@ -158,6 +158,7 @@ module.exports = withDocsInfra({ SLACK_FEEDBACKS_TOKEN: process.env.SLACK_FEEDBACKS_TOKEN, SOURCE_CODE_ROOT_URL: 'https://github.com/mui/material-ui/blob/master', // #default-branch-switch SOURCE_CODE_REPO: 'https://github.com/mui/material-ui', + GITHUB_TEMPLATE_DOCS_FEEDBACK: '4.docs-feedback.yml', BUILD_ONLY_ENGLISH_LOCALE: buildOnlyEnglishLocale, }, // Next.js provides a `defaultPathMap` argument, we could simplify the logic. diff --git a/docs/packages/markdown/parseMarkdown.js b/docs/packages/markdown/parseMarkdown.js index 3ce6ac8b0c29b1..d14d1c6cbe4092 100644 --- a/docs/packages/markdown/parseMarkdown.js +++ b/docs/packages/markdown/parseMarkdown.js @@ -249,6 +249,9 @@ function createRender(context) { ``, '', '', + ``, ``, ].join(''); }; @@ -458,6 +461,11 @@ ${headers.components +`); + rendered.unshift(` + + + `); docs[userLanguage] = { diff --git a/docs/src/modules/components/AppLayoutDocs.js b/docs/src/modules/components/AppLayoutDocs.js index c0b4b9887093ef..dfe039f798ba56 100644 --- a/docs/src/modules/components/AppLayoutDocs.js +++ b/docs/src/modules/components/AppLayoutDocs.js @@ -30,6 +30,9 @@ const Main = styled('main', { [theme.breakpoints.up('lg')]: { width: 'calc(100% - var(--MuiDocs-navDrawer-width))', }, + '& .markdown-body .comment-link-style': { + display: 'inline-block', + }, })); const StyledAppContainer = styled(AppContainer, { @@ -130,7 +133,7 @@ function AppLayoutDocs(props) { {location && } {children} - + diff --git a/docs/src/modules/components/AppLayoutDocsFooter.js b/docs/src/modules/components/AppLayoutDocsFooter.js index 2202051a18d4be..8e07dd740b2704 100644 --- a/docs/src/modules/components/AppLayoutDocsFooter.js +++ b/docs/src/modules/components/AppLayoutDocsFooter.js @@ -1,8 +1,10 @@ /* eslint-disable no-restricted-globals */ import * as React from 'react'; -import { styled } from '@mui/material/styles'; +import PropTypes from 'prop-types'; +import { styled, useTheme } from '@mui/material/styles'; import DialogActions from '@mui/material/DialogActions'; import TextField from '@mui/material/TextField'; +import Box from '@mui/material/Box'; import Collapse from '@mui/material/Collapse'; import Button from '@mui/material/Button'; import Divider from '@mui/material/Divider'; @@ -103,10 +105,10 @@ async function postFeedback(data) { } async function postFeedbackOnSlack(data) { - const { rating, comment } = data; + const { rating, comment, commentedSection } = data; if (!comment || comment.length < 10) { - return; + return 'ignored'; } /** @@ -152,9 +154,11 @@ async function postFeedbackOnSlack(data) { */ const simpleSlackMessage = [ - `New comment ${rating > 0 ? '👍' : '👎'}`, + `New comment ${rating === 1 ? '👍' : ''}${rating === 0 ? '👎' : ''}`, `>${comment.split('\n').join('\n>')}`, - `sent from ${window.location.href}`, + `sent from ${window.location.href}${ + commentedSection.text ? ` (from section ${commentedSection.text})` : '' + }`, ].join('\n\n'); try { @@ -163,8 +167,10 @@ async function postFeedbackOnSlack(data) { headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: JSON.stringify({ text: simpleSlackMessage }), }); + return 'sent'; } catch (error) { console.error(error); + return null; } } @@ -185,7 +191,7 @@ async function getUserFeedback(id) { } } -async function submitFeedback(page, rating, comment, language) { +async function submitFeedback(page, rating, comment, language, commentedSection) { const data = { id: getCookie('feedbackId'), page, @@ -195,18 +201,22 @@ async function submitFeedback(page, rating, comment, language) { language, }; - await postFeedbackOnSlack(data); - const result = await postFeedback(data); - if (result) { - document.cookie = `feedbackId=${result.id};path=/;max-age=31536000`; - setTimeout(async () => { - const userFeedback = await getUserFeedback(result.id); - if (userFeedback) { - document.cookie = `feedback=${JSON.stringify(userFeedback)};path=/;max-age=31536000`; - } - }); + const resultSlack = await postFeedbackOnSlack({ ...data, commentedSection }); + if (rating !== undefined) { + const resultVote = await postFeedback(data); + if (resultVote) { + document.cookie = `feedbackId=${resultVote.id};path=/;max-age=31536000`; + setTimeout(async () => { + const userFeedback = await getUserFeedback(resultVote.id); + if (userFeedback) { + document.cookie = `feedback=${JSON.stringify(userFeedback)};path=/;max-age=31536000`; + } + }); + } + return resultSlack && resultVote; } - return result; + + return resultSlack; } function getCurrentRating(pathname) { @@ -236,19 +246,37 @@ function usePageNeighbours() { return { prevPage, nextPage }; } -export default function AppLayoutDocsFooter() { +const EMPTY_SECTION = { hash: '', text: '' }; + +export default function AppLayoutDocsFooter(props) { + const { tableOfContents = [] } = props; + + const theme = useTheme(); const t = useTranslate(); const userLanguage = useUserLanguage(); const { activePage } = React.useContext(PageContext); const [rating, setRating] = React.useState(); const [comment, setComment] = React.useState(''); - const [commentOpen, setCommentOpen] = React.useState(false); const [snackbarOpen, setSnackbarOpen] = React.useState(false); const [snackbarMessage, setSnackbarMessage] = React.useState(false); const inputRef = React.useRef(); + const [commentOpen, setCommentOpen] = React.useState(false); + const [commentedSection, setCommentedSection] = React.useState(EMPTY_SECTION); const { nextPage, prevPage } = usePageNeighbours(); + const sectionOptions = React.useMemo( + () => + tableOfContents.flatMap((section) => [ + { + hash: section.hash, + text: section.text, + }, + ...section.children.map(({ hash, text }) => ({ hash, text })), + ]), + [tableOfContents], + ); + const setCurrentRatingFromCookie = React.useCallback(() => { if (activePage !== null) { setRating(getCurrentRating(activePage.pathname)); @@ -264,7 +292,13 @@ export default function AppLayoutDocsFooter() { setSnackbarMessage(t('feedbackFailed')); } - const result = await submitFeedback(activePage.pathname, rating, comment, userLanguage); + const result = await submitFeedback( + activePage.pathname, + rating, + comment, + userLanguage, + commentedSection, + ); if (result) { setSnackbarMessage(t('feedbackSubmitted')); } else { @@ -279,6 +313,12 @@ export default function AppLayoutDocsFooter() { setRating(vote); setCommentOpen(true); } + + // Manualy move focus if commment is already open. + // If the comment is closed, onEntered will call focus itself; + if (inputRef.current) { + inputRef.current.focus(); + } }; const handleChangeTextfield = (event) => { @@ -291,9 +331,20 @@ export default function AppLayoutDocsFooter() { processFeedback(); }; + // See https://github.com/mui/mui-toolpad/issues/1164 for context. + const handleKeyDownForm = (event) => { + const modifierKey = (event.metaKey || event.ctrlKey) && !event.shiftKey; + + if (event.key === 'Enter' && modifierKey) { + const submitButton = event.currentTarget.querySelector('[type="submit"]'); + submitButton.click(); + } + }; + const handleCancelComment = () => { setCommentOpen(false); setCurrentRatingFromCookie(); + setCommentedSection(EMPTY_SECTION); }; const handleEntered = () => { @@ -304,6 +355,27 @@ export default function AppLayoutDocsFooter() { setSnackbarOpen(false); }; + React.useEffect(() => { + const eventListener = (event) => { + const feedbackHash = event.target.getAttribute('data-feedback-hash'); + if (feedbackHash) { + const section = sectionOptions.find((item) => item.hash === feedbackHash) || EMPTY_SECTION; + setCommentOpen(true); + setCommentedSection(section); + + // Manualy move focus if commment is already open. + // If the comment is closed, onEntered will call focus itself; + if (inputRef.current) { + inputRef.current.focus(); + } + } + }; + document.addEventListener('click', eventListener); + return () => { + document.removeEventListener('click', eventListener); + }; + }, [sectionOptions]); + const hidePagePagination = activePage === null || activePage.ordered === false; return ( @@ -371,25 +443,42 @@ export default function AppLayoutDocsFooter() { )} - + + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
- - {t('feedbackTitle')} - -
- - {rating === 1 ? t('feedbackMessageUp') : t('feedbackMessageDown')} - + + {commentedSection.text ? ( + + ) : ( + + {rating === 1 ? t('feedbackMessageUp') : t('feedbackMessageDown')} + + )} -
- - - - + + + + + {rating !== 1 && ( + + {t('feedbackMessageToGitHub.usecases')} +
+ {t('feedbackMessageToGitHub.callToAction.text')} + + {t('feedbackMessageToGitHub.callToAction.link')} + +
+ )} +
@@ -415,3 +519,7 @@ export default function AppLayoutDocsFooter() { ); } + +AppLayoutDocsFooter.propTypes = { + tableOfContents: PropTypes.array, +}; diff --git a/docs/src/modules/components/MarkdownElement.js b/docs/src/modules/components/MarkdownElement.js index 59841af71cfa10..679276018f7a24 100644 --- a/docs/src/modules/components/MarkdownElement.js +++ b/docs/src/modules/components/MarkdownElement.js @@ -105,6 +105,7 @@ const Root = styled('div')( paddingLeft: 30, }, '& h1, & h2, & h3, & h4': { + position: 'relative', '& code': { fontSize: 'inherit', lineHeight: 'inherit', @@ -120,18 +121,19 @@ const Root = styled('div')( borderBottom: '1px solid currentColor', textDecoration: 'none', }, - '&:hover .anchor-link-style': { - display: 'inline-block', - textAlign: 'center', + '&:hover .anchor-link-style, & .comment-link-style': { lineHeight: '21.5px', + textAlign: 'center', marginLeft: 10, - height: '26px', - width: '26px', - background: `var(--muidocs-palette-primary-50, ${lightTheme.palette.primary[50]})`, + height: 26, + width: 26, + backgroundColor: `var(--muidocs-palette-primary-50, ${lightTheme.palette.primary[50]})`, border: '1px solid', borderColor: `var(--muidocs-palette-grey-200, ${lightTheme.palette.grey[200]})`, borderRadius: 8, color: `var(--muidocs-palette-text-secondary, ${lightTheme.palette.text.secondary})`, + cursor: 'pointer', + display: 'inline-block', '&:hover': { color: `var(--muidocs-palette-text-primary, ${lightTheme.palette.text.primary})`, }, @@ -139,6 +141,23 @@ const Root = styled('div')( width: '0.875rem', height: '0.875rem', fill: 'currentColor', + pointerEvents: 'none', + }, + }, + '& .comment-link-style': { + display: 'none', + position: 'absolute', + top: `calc(50% - ${26 / 2}px)`, + right: 0, + opacity: 0.5, + transition: theme.transitions.create('opacity', { + duration: theme.transitions.duration.shortest, + }), + '&:hover': { + opacity: 1, + }, + '& svg': { + verticalAlign: 'middle', }, }, }, @@ -441,9 +460,9 @@ const Root = styled('div')( color: `var(--muidocs-palette-grey-400, ${darkTheme.palette.grey[400]})`, }, '& h1, & h2, & h3, & h4': { - '&:hover .anchor-link-style': { + '&:hover .anchor-link-style, & .comment-link-style': { color: `var(--muidocs-palette-text-secondary, ${darkTheme.palette.text.secondary})`, - background: alpha(darkTheme.palette.primaryDark[800], 0.3), + backgroundColor: alpha(darkTheme.palette.primaryDark[800], 0.3), borderColor: `var(--muidocs-palette-primaryDark-500, ${darkTheme.palette.primaryDark[500]})`, '&:hover': { color: `var(--muidocs-palette-text-primary, ${darkTheme.palette.text.primary})`, diff --git a/docs/translations/translations.json b/docs/translations/translations.json index 1e92e78e3cf931..9c1bcc7347e977 100644 --- a/docs/translations/translations.json +++ b/docs/translations/translations.json @@ -69,11 +69,18 @@ "feedbackCommentLabel": "Comment", "feedbackFailed": "Couldn't submit feedback. Please try again later.", "feedbackMessage": "Was this page helpful?", - "feedbackMessageDown": "Please let us know what we could do to improve this page.", - "feedbackMessageUp": "Please let us know what we should keep doing more of.", + "feedbackMessageDown": "How can we improve this page? (optional)", + "feedbackMessageUp": "What did you like about this page? (optional)", + "feedbackSectionSpecific": "How can we improve the {{sectionName}} section? (optional)", + "feedbackMessageToGitHub": { + "usecases": "Is something broken? Do you need a reply to a problem you've encountered?", + "callToAction": { + "text": "Please ", + "link": "open an issue." + } + }, "feedbackNo": "No", "feedbackSubmitted": "Feedback submitted", - "feedbackTitle": "Thanks for your feedback!", "feedbackYes": "Yes", "footerCompany": "Company", "goToHome": "go to homepage",