mirror of
https://github.com/strapi/strapi.git
synced 2025-12-04 11:02:12 +00:00
commit
f7899f9970
@ -1,23 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const StyledLink = styled.a`
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 27px;
|
|
||||||
padding: 0 20px;
|
|
||||||
color: #919bae;
|
|
||||||
span,
|
|
||||||
svg {
|
|
||||||
margin: auto 0;
|
|
||||||
}
|
|
||||||
svg {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background-color: #f7f8f8;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #919bae;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default StyledLink;
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* StaticLinks
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { InjectionZone } from '../../../../shared/components';
|
|
||||||
import StyledLink from './StyledLink';
|
|
||||||
|
|
||||||
function StaticLinks() {
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const staticLinks = [
|
|
||||||
{
|
|
||||||
icon: 'book',
|
|
||||||
label: formatMessage({ id: 'app.components.LeftMenuFooter.documentation' }),
|
|
||||||
destination: 'https://strapi.io/documentation',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'file',
|
|
||||||
label: formatMessage({ id: 'app.static.links.cheatsheet' }),
|
|
||||||
destination: 'https://strapi-showcase.s3-us-west-2.amazonaws.com/CheatSheet.pdf',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ul className="list">
|
|
||||||
{staticLinks.map(link => {
|
|
||||||
const { icon, label, destination } = link;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={label}>
|
|
||||||
<StyledLink href={destination} target="_blank" rel="noopener noreferrer">
|
|
||||||
<FontAwesomeIcon icon={icon} />
|
|
||||||
<span>{label}</span>
|
|
||||||
</StyledLink>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<InjectionZone area="admin.tutorials.links" />
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StaticLinks;
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
import styled, { keyframes } from 'styled-components';
|
|
||||||
|
|
||||||
const fadeIn = keyframes`
|
|
||||||
0% {
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
5% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
const fadeOut = keyframes`
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
60% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
max-width: ${({ isOpen }) => (isOpen ? 'initial' : '0px')};
|
|
||||||
position: fixed;
|
|
||||||
right: 15px;
|
|
||||||
bottom: 15px;
|
|
||||||
button,
|
|
||||||
button:focus,
|
|
||||||
a {
|
|
||||||
cursor: pointer;
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.videosHeader {
|
|
||||||
padding: 25px 15px 18px 15px;
|
|
||||||
p {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 50%;
|
|
||||||
font-family: Lato;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 11px;
|
|
||||||
color: #5c5f66;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
&:last-of-type {
|
|
||||||
color: #5a9e06;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.visible {
|
|
||||||
opacity: 1;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
&.hidden {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.videosContent {
|
|
||||||
min-width: 320px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-right: 15px;
|
|
||||||
background-color: white;
|
|
||||||
box-shadow: 0 2px 4px 0 #e3e9f3;
|
|
||||||
border-radius: 3px;
|
|
||||||
overflow: hidden;
|
|
||||||
&.shown {
|
|
||||||
animation: ${fadeIn} 0.5s forwards;
|
|
||||||
}
|
|
||||||
&.hide {
|
|
||||||
min-width: 0;
|
|
||||||
animation: ${fadeOut} 0.5s forwards;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
padding: 0 0 8px 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
list-style: none;
|
|
||||||
&:last-of-type {
|
|
||||||
padding: 8px 0 10px 0;
|
|
||||||
border-top: 1px solid #f6f6f6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.openBtn {
|
|
||||||
float: right;
|
|
||||||
width: 38px;
|
|
||||||
height: 38px;
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
color: white;
|
|
||||||
background: #0e7de7;
|
|
||||||
box-shadow: 0px 2px 4px 0px rgba(227, 233, 243, 1);
|
|
||||||
i,
|
|
||||||
svg {
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
i:last-of-type,
|
|
||||||
svg:last-of-type {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
&.active {
|
|
||||||
i:first-of-type,
|
|
||||||
svg:first-of-type {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
i:last-of-type,
|
|
||||||
svg:last-of-type {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default Wrapper;
|
|
||||||
@ -1,41 +1,136 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faQuestion, faTimes } from '@fortawesome/free-solid-svg-icons';
|
import { faQuestion, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||||
import cn from 'classnames';
|
import { Box } from '@strapi/parts/Box';
|
||||||
|
import { Text } from '@strapi/parts/Text';
|
||||||
|
import { FocusTrap } from '@strapi/parts/FocusTrap';
|
||||||
import { useConfigurations } from '../../../hooks';
|
import { useConfigurations } from '../../../hooks';
|
||||||
import StaticLinks from './StaticLinks';
|
|
||||||
import Wrapper from './Wrapper';
|
const OnboardingWrapper = styled(Box)`
|
||||||
|
position: fixed;
|
||||||
|
bottom: ${({ theme }) => theme.spaces[2]};
|
||||||
|
right: ${({ theme }) => theme.spaces[2]};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Button = styled.button`
|
||||||
|
width: ${({ theme }) => theme.spaces[8]};
|
||||||
|
height: ${({ theme }) => theme.spaces[8]};
|
||||||
|
background: ${({ theme }) => theme.colors.primary600};
|
||||||
|
box-shadow: ${({ theme }) => theme.shadows.tableShadow};
|
||||||
|
border-radius: 50%;
|
||||||
|
svg {
|
||||||
|
color: ${({ theme }) => theme.colors.neutral0};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LinksWrapper = styled(Box)`
|
||||||
|
position: absolute;
|
||||||
|
bottom: ${({ theme }) => `${theme.spaces[9]}`};
|
||||||
|
right: 0;
|
||||||
|
width: ${200 / 16}rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledLink = styled.a`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: ${({ theme }) => theme.spaces[2]};
|
||||||
|
padding-left: ${({ theme }) => theme.spaces[5]};
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: ${({ theme }) => theme.colors.neutral600};
|
||||||
|
margin-right: ${({ theme }) => theme.spaces[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme }) => theme.colors.neutral100};
|
||||||
|
color: ${({ theme }) => theme.colors.neutral500};
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: ${({ theme }) => theme.colors.neutral700};
|
||||||
|
}
|
||||||
|
|
||||||
|
${[Text]} {
|
||||||
|
color: ${({ theme }) => theme.colors.neutral700};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${[Text]} {
|
||||||
|
color: ${({ theme }) => theme.colors.neutral600};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const Onboarding = () => {
|
const Onboarding = () => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
const { showTutorials } = useConfigurations();
|
const { showTutorials } = useConfigurations();
|
||||||
|
|
||||||
if (!showTutorials) {
|
if (!showTutorials) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <OnboardingVideos />;
|
const staticLinks = [
|
||||||
};
|
{
|
||||||
|
icon: 'book',
|
||||||
const OnboardingVideos = () => {
|
label: formatMessage({
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
id: 'app.components.LeftMenuFooter.documentation',
|
||||||
|
defaultMessage: 'Documentation',
|
||||||
|
}),
|
||||||
|
destination: 'https://strapi.io/documentation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'file',
|
||||||
|
label: formatMessage({ id: 'app.static.links.cheatsheet', defaultMessage: 'CheatSheet' }),
|
||||||
|
destination: 'https://strapi-showcase.s3-us-west-2.amazonaws.com/CheatSheet.pdf',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
setIsOpen(prev => !prev);
|
setIsOpen(prev => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper className="visible" isOpen={isOpen}>
|
<OnboardingWrapper>
|
||||||
<div className={cn('videosContent', isOpen ? 'shown' : 'hide')}>
|
<Button
|
||||||
<StaticLinks />
|
id="onboarding"
|
||||||
</div>
|
aria-label={formatMessage({
|
||||||
<div className="openBtn">
|
id: 'app.components.Onboarding.help.button',
|
||||||
<button onClick={handleClick} className={isOpen ? 'active' : ''} type="button">
|
defaultMessage: 'Help button',
|
||||||
<FontAwesomeIcon icon={faQuestion} />
|
})}
|
||||||
<FontAwesomeIcon icon={faTimes} />
|
onClick={handleClick}
|
||||||
<span />
|
>
|
||||||
</button>
|
{!isOpen && <FontAwesomeIcon icon={faQuestion} />}
|
||||||
</div>
|
{isOpen && <FontAwesomeIcon icon={faTimes} />}
|
||||||
</Wrapper>
|
</Button>
|
||||||
|
|
||||||
|
{/* FIX ME - replace with popover when overflow popover is fixed
|
||||||
|
+ when v4 mockups for onboarding component are ready */}
|
||||||
|
{isOpen && (
|
||||||
|
<FocusTrap onEscape={handleClick}>
|
||||||
|
<LinksWrapper
|
||||||
|
background="neutral0"
|
||||||
|
hasRadius
|
||||||
|
shadow="tableShadow"
|
||||||
|
paddingBottom={2}
|
||||||
|
paddingTop={2}
|
||||||
|
>
|
||||||
|
{staticLinks.map(link => (
|
||||||
|
<StyledLink
|
||||||
|
key={link.label}
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
href={link.destination}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={link.icon} />
|
||||||
|
<Text>{link.label}</Text>
|
||||||
|
</StyledLink>
|
||||||
|
))}
|
||||||
|
</LinksWrapper>
|
||||||
|
</FocusTrap>
|
||||||
|
)}
|
||||||
|
</OnboardingWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,80 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { ThemeProvider, lightTheme } from '@strapi/parts';
|
||||||
|
import Onboarding from '../index';
|
||||||
|
|
||||||
|
jest.mock('../../../../hooks', () => ({
|
||||||
|
useConfigurations: jest.fn(() => {
|
||||||
|
return { showTutorials: true };
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const App = (
|
||||||
|
<ThemeProvider theme={lightTheme}>
|
||||||
|
<IntlProvider locale="en" messages={{ en: {} }} textComponent="span">
|
||||||
|
<Onboarding />
|
||||||
|
</IntlProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('Onboarding', () => {
|
||||||
|
it('renders and matches the snapshot', async () => {
|
||||||
|
const {
|
||||||
|
container: { firstChild },
|
||||||
|
} = render(App);
|
||||||
|
|
||||||
|
expect(firstChild).toMatchInlineSnapshot(`
|
||||||
|
.c0 {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1 {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: #4945ff;
|
||||||
|
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c1 svg {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="c0"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Help button"
|
||||||
|
class="c1"
|
||||||
|
id="onboarding"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="svg-inline--fa fa-question fa-w-12 "
|
||||||
|
data-icon="question"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 384 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M202.021 0C122.202 0 70.503 32.703 29.914 91.026c-7.363 10.58-5.093 25.086 5.178 32.874l43.138 32.709c10.373 7.865 25.132 6.026 33.253-4.148 25.049-31.381 43.63-49.449 82.757-49.449 30.764 0 68.816 19.799 68.816 49.631 0 22.552-18.617 34.134-48.993 51.164-35.423 19.86-82.299 44.576-82.299 106.405V320c0 13.255 10.745 24 24 24h72.471c13.255 0 24-10.745 24-24v-5.773c0-42.86 125.268-44.645 125.268-160.627C377.504 66.256 286.902 0 202.021 0zM192 373.459c-38.196 0-69.271 31.075-69.271 69.271 0 38.195 31.075 69.27 69.271 69.27s69.271-31.075 69.271-69.271-31.075-69.27-69.271-69.27z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open links when button is clicked', () => {
|
||||||
|
render(App);
|
||||||
|
|
||||||
|
fireEvent.click(document.querySelector('#onboarding'));
|
||||||
|
expect(screen.getByText('Documentation')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -2,16 +2,12 @@ import React from 'react';
|
|||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
import { render, screen, waitFor } from '@testing-library/react';
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
|
import { ThemeProvider, lightTheme } from '@strapi/parts';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
import { useStrapiApp } from '@strapi/helper-plugin';
|
import { useStrapiApp } from '@strapi/helper-plugin';
|
||||||
import Theme from '../../../components/Theme';
|
|
||||||
import { useMenu } from '../../../hooks';
|
import { useMenu } from '../../../hooks';
|
||||||
import Admin from '../index';
|
import Admin from '../index';
|
||||||
|
|
||||||
jest.mock('react-intl', () => ({
|
|
||||||
// eslint-disable-next-line react/prop-types
|
|
||||||
FormattedMessage: ({ id }) => <p>{id}</p>,
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@strapi/helper-plugin', () => ({
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
LoadingIndicatorPage: () => <div>Loading</div>,
|
LoadingIndicatorPage: () => <div>Loading</div>,
|
||||||
useStrapiApp: jest.fn(() => ({ menu: [] })),
|
useStrapiApp: jest.fn(() => ({ menu: [] })),
|
||||||
@ -31,11 +27,13 @@ jest.mock('../../../components/LeftMenu', () => () => <div>menu</div>);
|
|||||||
jest.mock('../../HomePage', () => () => <div>HomePage</div>);
|
jest.mock('../../HomePage', () => () => <div>HomePage</div>);
|
||||||
|
|
||||||
const makeApp = history => (
|
const makeApp = history => (
|
||||||
<Theme>
|
<IntlProvider messages={{ en: {} }} textComponent="span" locale="en">
|
||||||
<Router history={history}>
|
<ThemeProvider theme={lightTheme}>
|
||||||
<Admin />
|
<Router history={history}>
|
||||||
</Router>
|
<Admin />
|
||||||
</Theme>
|
</Router>
|
||||||
|
</ThemeProvider>
|
||||||
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('<Admin />', () => {
|
describe('<Admin />', () => {
|
||||||
|
|||||||
@ -268,6 +268,7 @@
|
|||||||
"app.components.NotFoundPage.back": "Back to homepage",
|
"app.components.NotFoundPage.back": "Back to homepage",
|
||||||
"app.components.NotFoundPage.description": "Not Found",
|
"app.components.NotFoundPage.description": "Not Found",
|
||||||
"app.components.Official": "Official",
|
"app.components.Official": "Official",
|
||||||
|
"app.components.Onboarding.help.button": "Help button",
|
||||||
"app.components.Onboarding.label.completed": "% completed",
|
"app.components.Onboarding.label.completed": "% completed",
|
||||||
"app.components.Onboarding.title": "Get Started Videos",
|
"app.components.Onboarding.title": "Get Started Videos",
|
||||||
"app.components.MarketplaceBanner": "Discover plugins built by the community, and many more awesome things to kickstart your project, on Strapi Awesome.",
|
"app.components.MarketplaceBanner": "Discover plugins built by the community, and many more awesome things to kickstart your project, on Strapi Awesome.",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user