create-application-template 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.stylelintrc.js +13 -2
- package/ccrate/src/components/App.spec.tsx +16 -2
- package/ccrate/src/components/App.tsx +23 -3
- package/ccrate/src/styles/Logo.styled.ts +2 -2
- package/package.json +2 -1
- package/src/components/App.spec.tsx +16 -2
- package/src/components/App.tsx +23 -3
- package/src/components/Typewriter.spec.tsx +36 -0
- package/src/components/Typewriter.tsx +52 -0
- package/src/components/__snapshots__/Typewriter.spec.tsx.snap +56 -0
- package/src/styles/App.styled.ts +20 -9
- package/src/styles/Counter.styled.ts +5 -5
- package/src/styles/Logo.styled.ts +2 -5
package/.stylelintrc.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
// NOTE if you wish to use .css (see README.md "styles")
|
|
2
|
+
// - remove customSyntax and use "npm stylelint:css"
|
|
3
|
+
// - remove 'value-keyword-case', particularly 'ignoreKeywords'
|
|
4
|
+
|
|
1
5
|
module.exports = {
|
|
2
6
|
extends: [
|
|
3
7
|
'stylelint-config-standard',
|
|
4
8
|
'stylelint-config-recess-order',
|
|
5
9
|
'stylelint-no-unsupported-browser-features',
|
|
6
10
|
],
|
|
7
|
-
|
|
8
|
-
// if you wish to use .css (see README.md "styles")
|
|
11
|
+
|
|
9
12
|
customSyntax: 'postcss-styled-components',
|
|
10
13
|
ignoreFiles: [],
|
|
11
14
|
rules: {
|
|
@@ -13,5 +16,13 @@ module.exports = {
|
|
|
13
16
|
'keyframes-name-pattern': null,
|
|
14
17
|
'comment-empty-line-before': 'never',
|
|
15
18
|
'declaration-empty-line-before': 'never',
|
|
19
|
+
'value-keyword-case': [
|
|
20
|
+
'lower',
|
|
21
|
+
{
|
|
22
|
+
'ignoreKeywords': [
|
|
23
|
+
'/^POSTCSS_styled-components_\\d+$/',
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
16
27
|
},
|
|
17
28
|
}
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react'
|
|
1
|
+
import { render, screen, act } from '@testing-library/react'
|
|
2
2
|
import 'jest-styled-components'
|
|
3
3
|
import { App } from './App'
|
|
4
4
|
|
|
5
|
+
jest.useFakeTimers()
|
|
6
|
+
|
|
5
7
|
test('renders title', () => {
|
|
6
8
|
render(<App />)
|
|
7
9
|
|
|
10
|
+
const text = 'Create Containerized App Template'
|
|
11
|
+
|
|
12
|
+
for (let i = 1; i <= text.length; i++) {
|
|
13
|
+
act(() => {
|
|
14
|
+
jest.advanceTimersByTime(50)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
8
18
|
expect(screen).toMatchSnapshot()
|
|
9
|
-
expect(screen.getByText(
|
|
19
|
+
expect(screen.getByText(text)).toBeInTheDocument()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
jest.useRealTimers()
|
|
10
24
|
})
|
|
@@ -7,6 +7,7 @@ import { StyledLogo } from '../styles/Logo.styled'
|
|
|
7
7
|
import '../styles/env.css'
|
|
8
8
|
import logo from '../assets/logo.svg'
|
|
9
9
|
import { Counter } from './Counter'
|
|
10
|
+
import { Typewriter } from './Typewriter'
|
|
10
11
|
|
|
11
12
|
export const App: FC = () => {
|
|
12
13
|
const TemplateLink: FC = () => {
|
|
@@ -26,9 +27,28 @@ export const App: FC = () => {
|
|
|
26
27
|
<GlobalStyles />
|
|
27
28
|
<app.StyledContainer>
|
|
28
29
|
<app.StyledHeader>
|
|
29
|
-
<h1>
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
<h1>
|
|
31
|
+
<Typewriter
|
|
32
|
+
text={'Create Containerized App Template'}
|
|
33
|
+
speed={50}
|
|
34
|
+
/>
|
|
35
|
+
</h1>
|
|
36
|
+
<h2>
|
|
37
|
+
<Typewriter
|
|
38
|
+
text={'An app template containerized via Docker!'}
|
|
39
|
+
speed={50}
|
|
40
|
+
delay={300}
|
|
41
|
+
/>
|
|
42
|
+
</h2>
|
|
43
|
+
<h2>
|
|
44
|
+
<Typewriter
|
|
45
|
+
text={'Access the CCrATe template'}
|
|
46
|
+
speed={50}
|
|
47
|
+
delay={1300}
|
|
48
|
+
>
|
|
49
|
+
<TemplateLink />
|
|
50
|
+
</Typewriter>
|
|
51
|
+
</h2>
|
|
32
52
|
</app.StyledHeader>
|
|
33
53
|
<app.StyledSection>
|
|
34
54
|
<code className='card--env'>[NODE_ENV={process.env.NODE_ENV}]</code>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { styled, keyframes } from 'styled-components'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const pulse = keyframes`
|
|
4
4
|
from {
|
|
5
5
|
transform: rotate(15deg);
|
|
6
6
|
}
|
|
@@ -16,6 +16,6 @@ export const StyledLogo = styled.img`
|
|
|
16
16
|
transform-origin: top;
|
|
17
17
|
|
|
18
18
|
@media (prefers-reduced-motion: no-preference) {
|
|
19
|
-
animation: ${
|
|
19
|
+
animation: ${pulse} 3.5s ease-in-out infinite alternate-reverse;
|
|
20
20
|
}
|
|
21
21
|
`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-application-template",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "provides a configured application template for you to build upon",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"@typescript-eslint/parser": "6.5.0",
|
|
76
76
|
"babel-jest": "29.7.0",
|
|
77
77
|
"babel-loader": "9.1.3",
|
|
78
|
+
"babel-plugin-styled-components": "2.1.4",
|
|
78
79
|
"babel-plugin-transform-require-context": "0.1.1",
|
|
79
80
|
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
|
80
81
|
"confusing-browser-globals": "1.0.11",
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react'
|
|
1
|
+
import { render, screen, act } from '@testing-library/react'
|
|
2
2
|
import 'jest-styled-components'
|
|
3
3
|
import { App } from './App'
|
|
4
4
|
|
|
5
|
+
jest.useFakeTimers()
|
|
6
|
+
|
|
5
7
|
test('renders title', () => {
|
|
6
8
|
render(<App />)
|
|
7
9
|
|
|
10
|
+
const text = 'Create Application Template'
|
|
11
|
+
|
|
12
|
+
for (let i = 1; i <= text.length; i++) {
|
|
13
|
+
act(() => {
|
|
14
|
+
jest.advanceTimersByTime(50)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
8
18
|
expect(screen).toMatchSnapshot()
|
|
9
|
-
expect(screen.getByText(
|
|
19
|
+
expect(screen.getByText(text)).toBeInTheDocument()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
jest.useRealTimers()
|
|
10
24
|
})
|
package/src/components/App.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { StyledLogo } from '../styles/Logo.styled'
|
|
|
7
7
|
import '../styles/env.css'
|
|
8
8
|
import logo from '../assets/logo.svg'
|
|
9
9
|
import { Counter } from './Counter'
|
|
10
|
+
import { Typewriter } from './Typewriter'
|
|
10
11
|
|
|
11
12
|
export const App: FC = () => {
|
|
12
13
|
const TemplateLink: FC = () => {
|
|
@@ -26,9 +27,28 @@ export const App: FC = () => {
|
|
|
26
27
|
<GlobalStyles />
|
|
27
28
|
<app.StyledContainer>
|
|
28
29
|
<app.StyledHeader>
|
|
29
|
-
<h1>
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
<h1>
|
|
31
|
+
<Typewriter
|
|
32
|
+
text={'Create Application Template'}
|
|
33
|
+
speed={50}
|
|
34
|
+
/>
|
|
35
|
+
</h1>
|
|
36
|
+
<h2>
|
|
37
|
+
<Typewriter
|
|
38
|
+
text={'Configured and under your control!'}
|
|
39
|
+
speed={50}
|
|
40
|
+
delay={300}
|
|
41
|
+
/>
|
|
42
|
+
</h2>
|
|
43
|
+
<h2>
|
|
44
|
+
<Typewriter
|
|
45
|
+
text={'Access the template'}
|
|
46
|
+
speed={50}
|
|
47
|
+
delay={1300}
|
|
48
|
+
>
|
|
49
|
+
<TemplateLink />
|
|
50
|
+
</Typewriter>
|
|
51
|
+
</h2>
|
|
32
52
|
</app.StyledHeader>
|
|
33
53
|
<app.StyledSection>
|
|
34
54
|
<code className='card--env'>[NODE_ENV={process.env.NODE_ENV}]</code>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { render, screen, act } from '@testing-library/react'
|
|
2
|
+
import 'jest-styled-components'
|
|
3
|
+
import { Typewriter } from './Typewriter'
|
|
4
|
+
|
|
5
|
+
jest.useFakeTimers()
|
|
6
|
+
|
|
7
|
+
test('renders text and children', () => {
|
|
8
|
+
const text = 'testing'
|
|
9
|
+
|
|
10
|
+
render(
|
|
11
|
+
<Typewriter
|
|
12
|
+
text={text}
|
|
13
|
+
speed={50}
|
|
14
|
+
>
|
|
15
|
+
<div data-testid='child-element'>child</div>
|
|
16
|
+
</Typewriter>
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
for (let i = 1; i <= text.length; i++) {
|
|
20
|
+
// full text available only after all
|
|
21
|
+
// timeouts complete, see Typewriter.tsx
|
|
22
|
+
act(() => {
|
|
23
|
+
jest.advanceTimersByTime(50)
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
expect(screen).toMatchSnapshot()
|
|
28
|
+
expect(screen.getByText(text)).toBeInTheDocument()
|
|
29
|
+
expect(screen.getByTestId('child-element')).toBeInTheDocument()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
// reset real timers so other tests
|
|
34
|
+
// are not affected by mock timers
|
|
35
|
+
jest.useRealTimers()
|
|
36
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
FC,
|
|
5
|
+
ReactElement,
|
|
6
|
+
PropsWithChildren,
|
|
7
|
+
} from 'react'
|
|
8
|
+
|
|
9
|
+
export const Typewriter: FC<
|
|
10
|
+
PropsWithChildren<{
|
|
11
|
+
text:string,
|
|
12
|
+
speed:number,
|
|
13
|
+
delay?:number,
|
|
14
|
+
children?:ReactElement,
|
|
15
|
+
}>
|
|
16
|
+
> = ({
|
|
17
|
+
text,
|
|
18
|
+
speed,
|
|
19
|
+
delay = 0,
|
|
20
|
+
children,
|
|
21
|
+
}) => {
|
|
22
|
+
const [displayedText, setDisplayedText] = useState('')
|
|
23
|
+
const [currentIndex, setCurrentIndex] = useState(0)
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const mySpeed = (currentIndex === 0) ? delay + speed : speed
|
|
27
|
+
|
|
28
|
+
if (currentIndex < text.length) {
|
|
29
|
+
const timeoutId = setTimeout(() => {
|
|
30
|
+
setDisplayedText((prevText) => prevText + text.charAt(currentIndex))
|
|
31
|
+
setCurrentIndex((prevIndex) => prevIndex + 1)
|
|
32
|
+
}, mySpeed)
|
|
33
|
+
|
|
34
|
+
return () => clearTimeout(timeoutId)
|
|
35
|
+
}
|
|
36
|
+
}, [
|
|
37
|
+
currentIndex,
|
|
38
|
+
text,
|
|
39
|
+
speed,
|
|
40
|
+
delay,
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
{displayedText}
|
|
46
|
+
{
|
|
47
|
+
(currentIndex === text.length && children) &&
|
|
48
|
+
<> {children}</>
|
|
49
|
+
}
|
|
50
|
+
</>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`renders text and children 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"debug": [Function],
|
|
6
|
+
"findAllByAltText": [Function],
|
|
7
|
+
"findAllByDisplayValue": [Function],
|
|
8
|
+
"findAllByLabelText": [Function],
|
|
9
|
+
"findAllByPlaceholderText": [Function],
|
|
10
|
+
"findAllByRole": [Function],
|
|
11
|
+
"findAllByTestId": [Function],
|
|
12
|
+
"findAllByText": [Function],
|
|
13
|
+
"findAllByTitle": [Function],
|
|
14
|
+
"findByAltText": [Function],
|
|
15
|
+
"findByDisplayValue": [Function],
|
|
16
|
+
"findByLabelText": [Function],
|
|
17
|
+
"findByPlaceholderText": [Function],
|
|
18
|
+
"findByRole": [Function],
|
|
19
|
+
"findByTestId": [Function],
|
|
20
|
+
"findByText": [Function],
|
|
21
|
+
"findByTitle": [Function],
|
|
22
|
+
"getAllByAltText": [Function],
|
|
23
|
+
"getAllByDisplayValue": [Function],
|
|
24
|
+
"getAllByLabelText": [Function],
|
|
25
|
+
"getAllByPlaceholderText": [Function],
|
|
26
|
+
"getAllByRole": [Function],
|
|
27
|
+
"getAllByTestId": [Function],
|
|
28
|
+
"getAllByText": [Function],
|
|
29
|
+
"getAllByTitle": [Function],
|
|
30
|
+
"getByAltText": [Function],
|
|
31
|
+
"getByDisplayValue": [Function],
|
|
32
|
+
"getByLabelText": [Function],
|
|
33
|
+
"getByPlaceholderText": [Function],
|
|
34
|
+
"getByRole": [Function],
|
|
35
|
+
"getByTestId": [Function],
|
|
36
|
+
"getByText": [Function],
|
|
37
|
+
"getByTitle": [Function],
|
|
38
|
+
"logTestingPlaygroundURL": [Function],
|
|
39
|
+
"queryAllByAltText": [Function],
|
|
40
|
+
"queryAllByDisplayValue": [Function],
|
|
41
|
+
"queryAllByLabelText": [Function],
|
|
42
|
+
"queryAllByPlaceholderText": [Function],
|
|
43
|
+
"queryAllByRole": [Function],
|
|
44
|
+
"queryAllByTestId": [Function],
|
|
45
|
+
"queryAllByText": [Function],
|
|
46
|
+
"queryAllByTitle": [Function],
|
|
47
|
+
"queryByAltText": [Function],
|
|
48
|
+
"queryByDisplayValue": [Function],
|
|
49
|
+
"queryByLabelText": [Function],
|
|
50
|
+
"queryByPlaceholderText": [Function],
|
|
51
|
+
"queryByRole": [Function],
|
|
52
|
+
"queryByTestId": [Function],
|
|
53
|
+
"queryByText": [Function],
|
|
54
|
+
"queryByTitle": [Function],
|
|
55
|
+
}
|
|
56
|
+
`;
|
package/src/styles/App.styled.ts
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
import { styled } from 'styled-components'
|
|
1
|
+
import { styled, keyframes } from 'styled-components'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const fadeIn = keyframes`
|
|
4
|
+
0% {
|
|
5
|
+
opacity: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
100% {
|
|
9
|
+
opacity: 1;
|
|
10
|
+
}
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
export const StyledContainer = styled.div`
|
|
4
14
|
display: flex;
|
|
5
15
|
flex-direction: column;
|
|
6
16
|
align-items: center;
|
|
7
17
|
justify-content: center;
|
|
8
18
|
min-height: 100vh;
|
|
9
19
|
font-size: max(1em, 18px);
|
|
10
|
-
color: ${theme.colors.palette.primary};
|
|
11
|
-
background-color: ${theme.colors.palette.background};
|
|
12
|
-
`
|
|
20
|
+
color: ${({ theme }) => theme.colors.palette.primary};
|
|
21
|
+
background-color: ${({ theme }) => theme.colors.palette.background};
|
|
22
|
+
`
|
|
13
23
|
|
|
14
24
|
export const StyledHeader = styled.header`
|
|
15
25
|
text-align: center;
|
|
@@ -19,13 +29,14 @@ export const StyledSection = styled.section`
|
|
|
19
29
|
display: flex;
|
|
20
30
|
flex-direction: column;
|
|
21
31
|
margin: 2vh;
|
|
32
|
+
animation: 3s ${fadeIn} ease;
|
|
22
33
|
`
|
|
23
34
|
|
|
24
|
-
export const StyledLink = styled.a
|
|
25
|
-
color: ${theme.colors.link.regular};
|
|
35
|
+
export const StyledLink = styled.a`
|
|
36
|
+
color: ${({ theme }) => theme.colors.link.regular};
|
|
26
37
|
transition: 0.3s;
|
|
27
38
|
|
|
28
39
|
&:hover {
|
|
29
|
-
color: ${theme.colors.link.hover};
|
|
40
|
+
color: ${({ theme }) => theme.colors.link.hover};
|
|
30
41
|
}
|
|
31
|
-
`
|
|
42
|
+
`
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { styled } from 'styled-components'
|
|
2
2
|
|
|
3
|
-
export const StyledCounter = styled.button
|
|
3
|
+
export const StyledCounter = styled.button`
|
|
4
4
|
height: 40px;
|
|
5
5
|
padding: 10px;
|
|
6
6
|
font-size: 18px;
|
|
7
|
-
color: ${theme.colors.palette.primary};
|
|
8
|
-
background-color: ${theme.colors.palette.background};
|
|
9
|
-
border: 1px solid ${theme.colors.palette.primary};
|
|
7
|
+
color: ${({ theme }) => theme.colors.palette.primary};
|
|
8
|
+
background-color: ${({ theme }) => theme.colors.palette.background};
|
|
9
|
+
border: 1px solid ${({ theme }) => theme.colors.palette.primary};
|
|
10
10
|
border-radius: 8px;
|
|
11
11
|
transition-duration: 0.5s;
|
|
12
12
|
|
|
13
13
|
&:hover {
|
|
14
14
|
border-radius: 20px;
|
|
15
15
|
}
|
|
16
|
-
`
|
|
16
|
+
`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { styled, keyframes } from 'styled-components'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const pulse = keyframes`
|
|
4
4
|
0% {
|
|
5
5
|
opacity: 0.2;
|
|
6
6
|
transform: scale(0.8);
|
|
@@ -20,8 +20,5 @@ const logoAnimation = keyframes`
|
|
|
20
20
|
export const StyledLogo = styled.img`
|
|
21
21
|
height: 240px;
|
|
22
22
|
pointer-events: none;
|
|
23
|
-
|
|
24
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
25
|
-
animation: ${logoAnimation} infinite 10s ease;
|
|
26
|
-
}
|
|
23
|
+
animation: ${pulse} infinite 10s ease;
|
|
27
24
|
`
|