diff --git a/README.md b/README.md index 8fcf34746..cb973bc60 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,9 @@ - [TypeScript Frontend](frontend/) - [Swift](swift/) - [UI 设计师](design/) -- [TypeScript Backend](backend/) -- [TypeScript Fullstack](fullstack/) -北京: -- [TypeScript Backend](backend/) -- [TypeScript Fullstack(偏 Backend)](fullstack/) +深圳/香港: +- [Support Engineer](it-support-engineer/) 链接中有更为具体的 JD。 @@ -129,4 +126,4 @@ Fork 当前仓库并在相关文件夹下找到你的作业。完成作业后提 1. 面试时我们会考察编程语言的掌握能力(TS/Swift)。 2. 我们看重解决问题的能力,以及发散思维的能力。 -3. 所有办公地点在市区,交通便利。 +3. 所有办公地点在市区,交通便利。 \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css index ebdb2969e..fd7a3f038 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,14 +1,44 @@ .App { text-align: center; position: relative; + width: 100vw; + height: 100vh; } -.title { - font-size: 56px; - font-weight: 600; +.container { + width: 100%; + height: 100%; + overflow: hidden; + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding-top: 170px; + padding-bottom: 100px; + box-sizing: border-box; + background-position: center bottom 100px; + background-size: auto 60%; + background-repeat: no-repeat; } -.text { - font-size: 28px; - font-weight: 400; +.container .text-content { + position: relative; + z-index: 1; } + +.container .text-content .title { + font-size: 60px; + line-height: 60px; + color: inherit; + margin: 0; + text-align: center; + white-space: pre; +} + +.container .text-content .content { + font-size: 34px; + text-align: center; + line-height: 50px; + white-space: pre; +} \ No newline at end of file diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 4db7ebc25..50feef9c0 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; +import React from 'react' import { render } from '@testing-library/react'; import App from './App'; -test('renders learn react link', () => { - const { getByText } = render(); - const linkElement = getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); +describe('App', () => { + it('component should be render correctly', () => { + const { rerender } = render(); + expect(() => { + rerender(); + }).not.toBeNull(); + }); +}); \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c57638ada..65fc2c098 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,54 @@ -import './App.css'; +import Carousel from './components/Carousel/index' +import imgIPhone from './assets/iphone.png' +import imgTablet from './assets/tablet.png' +import imgAirPods from './assets/airpods.png' + +import './App.css' + +export interface CarouselDataItem{ + id: string + color: string + backgroundColor: string + title?: string[] + contents?: string[] + bg: string +} + + +const data: CarouselDataItem[] = [ + { + id: '1', + color: '#fff', + backgroundColor: '#111111', + title: ['xPhone'], + contents: ['Lots to love. Less to spend.', 'Starting at $399.'], + bg: imgIPhone + }, + { + id: '2', + color: '#000', + backgroundColor: '#FAFAFA', + title: ['Tablet'], + contents: ['Just the right amount of everything.'], + bg: imgTablet + }, + { + id: '3', + color: '#000', + backgroundColor: '#F1F1F1', + title: ['Buy a Tablet or xPhone for college.', 'Get airPods.'], + bg: imgAirPods + } +] function App() { - return
{/* write your component here */}
; + return ( +
+ +
+ ) } -export default App; +export default App; \ No newline at end of file diff --git a/frontend/src/components/Carousel/index.css b/frontend/src/components/Carousel/index.css new file mode 100644 index 000000000..a44c8e355 --- /dev/null +++ b/frontend/src/components/Carousel/index.css @@ -0,0 +1,81 @@ +@keyframes progress { + from { + transform: scaleX(0); + } + to { + transform: scaleX(1); + } + } + + .carousel { + width: 100%; + height: 100%; + overflow-x: hidden; + } + + .carousel .carousel-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-wrap: nowrap; + padding: 0; + margin: 0; + transition: transform 0.3s; + } + + .carousel .carousel-wrapper .carousel-item { + width: 100%; + height: 100%; + flex-shrink: 0; + position: relative; + list-style: none; + } + + .carousel .indicators { + position: absolute; + left: 50%; + transform: translateX(-50%); + bottom: 30px; + display: flex; + align-items: center; + padding: 0; + } + + .carousel .indicators .indicator { + list-style: none; + box-sizing: border-box; + width: 40px; + height: 20px; + border-radius: 1px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + } + + .carousel .indicators .indicator + .indicator { + margin-left: 8px; + } + + .carousel .indicators .indicator .indicator__track { + display: block; + width: 100%; + height: 2px; + background: rgb(140, 140, 140); + } + + .carousel .indicators .indicator .indicator__bar{ + display: block; + border-radius: 1px; + width: 100%; + height: 2px; + background-color: #fff; + transform: scaleX(0); + transform-origin: left; + } + + + .carousel .indicators .indicator.active .indicator__bar { + animation: progress linear; + animation-duration: var(--indicator-animation-duration); + } \ No newline at end of file diff --git a/frontend/src/components/Carousel/index.tsx b/frontend/src/components/Carousel/index.tsx new file mode 100644 index 000000000..0938839e4 --- /dev/null +++ b/frontend/src/components/Carousel/index.tsx @@ -0,0 +1,70 @@ +/* * + * @author Ethan + * @email 271757749@qq.com + * @datetime 2023/04/19 + * */ + +import { type FC, type CSSProperties, useEffect, useState } from 'react' +import './index.css' + +import { CarouselDataItem } from '../../App' + +export interface CarouselProps { + items: CarouselDataItem[] + delay?: number +} + +const Carousel: FC = ({ items, delay = 3000}) => { + const [active, setActive] = useState(0) + useEffect(() => { + const timer = setTimeout(() => setActive((active + 1) % items.length), delay) + return () => clearTimeout(timer) + }, [items, delay, active]) + + return ( +
+
    + {items.map((item, idx) => ( +
  • +
    +
    + {item.title &&

    {item.title.join('\r\n')}

    } + {item.contents &&

    {item.contents.join('\r\n')}

    } +
    +
    +
  • + ))} +
+
    + {items.map((item, idx) => ( +
  • { + setActive(idx) + } + } + > + + + +
  • + ))} +
+
+ ) +} + +export default Carousel diff --git a/frontend/src/components/index.test.tsx b/frontend/src/components/index.test.tsx new file mode 100644 index 000000000..b70e4b3ae --- /dev/null +++ b/frontend/src/components/index.test.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import Carousel from '../components/Carousel/index' +import imgIPhone from '../assets/iphone.png' +import imgTablet from '../assets/tablet.png' +import imgAirPods from '../assets/airpods.png' + +beforeEach(()=>{ + jest.clearAllMocks(); +}) + +describe('renders carousel component correctly', () => { + const Component = () => ( + + ) + test('click dots',()=>{ + const { container } = render() + const dots = container.children[0].querySelectorAll(".indicator__track"); + //click dots[0] show first page, the title should be xPhone + fireEvent.click(dots[0]); + expect(screen.getByText("xPhone")).not.toBeNull() + //click dots[1] show second page, the title should be Tablet + fireEvent.click(dots[1]); + expect(screen.getByText("Tablet")).not.toBeNull() + //click dots[2] show second page, the title should be Tablet + fireEvent.click(dots[2]); + expect(screen.getByText("Buy a Tablet or xPhone for college. Get airPods.")).not.toBeNull() +})});