Commit 481ba21c by zhanghaozhe

Merge branch 'storybook' into dev

# Conflicts:
#	src/utils/index.js
parents e6e49608 a2e1f415
const postcssNormalize = require('postcss-normalize')
const path = require('path')
module.exports = {
stories: ['../src/common/**/*.stories.tsx'],
addons: [
{
name: '@storybook/preset-typescript',
options: {
tsLoaderOptions: {
configFile: path.resolve(__dirname, '../tsconfig.json'),
},
},
},
'@storybook/addon-actions',
'@storybook/addon-links',
'@storybook/addon-knobs',
],
webpackFinal: async config => {
config.module.rules.push({
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
/*require('postcss-px-to-viewport')({
viewportWidth: 375,
unitPrecision: 6,
selectorBlackList: ['skip-vw'],
}),*/
postcssNormalize(),
],
sourceMap: false,
},
},
{
loader: 'sass-loader',
},
],
})
return config;
},
};
import './style.scss'
\ No newline at end of file
.shadow {
display: inline-block;
background: rgba(255, 255, 255, 1);
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
.purchase {
width: 61px;
height: 18px;
background: rgba(0, 153, 255, 1);
border-radius: 9px;
color: #fff;
font-size: 12px;
border: 0;
outline: 0;
-webkit-appearance: none;
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -13,6 +13,7 @@
"@testing-library/user-event": "^7.2.1",
"@types/jest": "^24.9.1",
"@types/node": "^12.12.53",
"@types/qs": "^6.9.4",
"@types/react": "^16.9.43",
"@types/react-dom": "^16.9.8",
"@types/react-redux": "^7.1.9",
......@@ -63,9 +64,9 @@
"json-stringify-safe": "^5.0.1",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"lodash": "^4.17.15",
"lodash": "^4.17.19",
"mini-css-extract-plugin": "0.9.0",
"node-sass": "^4.13.0",
"node-sass": "^4.14.1",
"optimize-css-assets-webpack-plugin": "5.0.3",
"pnp-webpack-plugin": "1.6.4",
"postcss-flexbugs-fixes": "4.1.0",
......@@ -74,7 +75,7 @@
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "4.0.1",
"prop-types": "^15.7.2",
"qrcode": "^1.3.3",
"qrcode": "^1.4.4",
"qs": "^6.7.0",
"react": "^16.13.1",
"react-ace": "^8.0.0",
......@@ -86,7 +87,7 @@
"react-lazy-load": "^3.0.13",
"react-mobile-swiper": "^1.1.4",
"react-redux": "^7.0.2",
"react-router-dom": "^5.0.1",
"react-router-dom": "^5.2.0",
"react-spinners": "^0.5.4",
"react-sticky": "^6.0.3",
"redux": "^4.0.1",
......@@ -96,7 +97,6 @@
"resolve": "1.15.0",
"resolve-url-loader": "3.1.1",
"sass-loader": "8.0.2",
"sass-resources-loader": "^2.0.0",
"semver": "6.3.0",
"socket.io": "^2.2.0",
"store2": "^2.11.2",
......@@ -109,14 +109,16 @@
"video.js": "^7.6.5",
"web-launch-app": "^2.1.9",
"webpack": "4.42.0",
"webpack-dev-server": "3.10.3",
"webpack-dev-server": "^3.11.0",
"webpack-manifest-plugin": "2.2.0",
"workbox-webpack-plugin": "4.3.1"
},
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js"
"test": "node scripts/test.js",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"eslintConfig": {
"extends": "react-app"
......@@ -194,13 +196,20 @@
},
"devDependencies": {
"@babel/runtime": "^7.7.2",
"@storybook/addon-actions": "^5.3.19",
"@storybook/addon-knobs": "^5.3.19",
"@storybook/addon-links": "^5.3.19",
"@storybook/addons": "^5.3.19",
"@storybook/preset-typescript": "^3.0.0",
"@storybook/react": "^5.3.19",
"babel-plugin-import": "^1.11.0",
"browserslist": "^4.6.6",
"caniuse-lite": "^1.0.30000989",
"classnames": "^2.2.6",
"fork-ts-checker-webpack-plugin": "^5.0.14",
"mockjs": "^1.0.1-beta3",
"postcss-px-to-viewport": "^1.1.0",
"sass-resources-loader": "^2.0.0"
"ts-loader": "^8.0.2"
},
"theme": "./src/assets/theme/config.js"
}
@mixin fontFamily($font){
font-family: $font, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;;
}
\ No newline at end of file
@import "../index";
.course-card-h {
position: relative;
display: flex;
width: 351px;
height: 108px;
.show {
position: relative;
flex: 0 0 auto;
width: 150px;
height: 108px;
margin-right: 15px;
img {
width: 100%;
height: 100%;
border-radius: 3px;
}
}
.info {
position: relative;
width: 181px;
height: 100%;
.title {
width: 100%;
margin-bottom: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
color: #333;
}
.subtitle {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
-webkit-line-clamp: 2;
font-size: 14px;
color: #666;
}
.status {
position: absolute;
left: 0;
bottom: 5px;
width: 100%;
}
}
}
\ No newline at end of file
import React, {ReactNode, ReactElement} from 'react';
import {handleNavigation, Navigation} from "../index";
import {RequireAtLeastOne} from 'src/utils/types'
import {History} from "history";
import './index.scss'
export interface CoursePropsBasic {
image: string
title: string
courseId: number
status: ReactNode
navigate?: (courseId: number) => void
history?: History
subtitle?: string
tag?: ReactElement
marketing?: string
}
export type CourseProps = RequireAtLeastOne<CoursePropsBasic, 'history' | 'navigate'>
const CourseCardH: React.FC<CourseProps> = ({image, title, subtitle, status, navigate, courseId, tag, history, marketing}) => {
return (
<div className={'course-card course-card-h'} onClick={(e) => {
if (navigate) {
handleNavigation({e, courseId, navigate})
} else {
handleNavigation({e, courseId, history} as Navigation)
}
}}>
<div className="show">
<img src={image} alt={title}/>
{marketing && <div className="marketing">{marketing}</div>}
{tag}
</div>
<div className="info">
<div className="title">{title}</div>
{subtitle && <div className="subtitle">{subtitle}</div>}
<div className="status">
{status}
</div>
</div>
</div>
);
}
CourseCardH.displayName = 'CourseCardH'
export default CourseCardH;
\ No newline at end of file
@import "src/assets/css/utils";
@import "../index";
.course-card-v {
width: 165px;
.show {
position: relative;
width: 100%;
height: 119px;
margin-bottom: 6px;
img {
width: 100%;
height: 100%;
border-radius: 4px;
}
}
.title {
margin-bottom: 30px;
font-size: 15px;
color: #525C65;
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
-webkit-line-clamp: 2;
@include fontFamily('PingFangSC-Regular');
}
}
\ No newline at end of file
import React from 'react'
import {CourseProps} from '../course-card-h'
import {handleNavigation, Navigation} from "../index";
import './index.scss'
const CourseCardV: React.FC<CourseProps> = ({title, image, courseId, status, tag, navigate, history, marketing}) => {
return <div className={'course-card-v'} onClick={(e) => {
navigate ? handleNavigation({e, courseId, navigate})
: handleNavigation({e, courseId, history} as Navigation)
}}>
<div className="show">
<img src={image} alt={title}/>
{marketing && <div className="marketing">{marketing}</div>}
{tag}
</div>
<div className="title">{title}</div>
<div className="status">{status}</div>
</div>
}
export default CourseCardV
\ No newline at end of file
import React from "react";
import {withKnobs, number, text} from "@storybook/addon-knobs";
import {action} from "@storybook/addon-actions";
import CourseCardH from "./course-card-h";
import CourseCardV from "./course-card-v";
export const courseData = {
courseId: 140,
title: '三月面试求职班',
subtitle: '搞定算法 直通BAT',
image: 'https://julyedu-img-public.oss-cn-beijing.aliyuncs.com/Public/Image/20a86c1353.jpg',
marketing: '拼团减100元'
}
export default {
title: 'course-card',
component: CourseCardH,
decorators: [withKnobs, (story: () => React.ReactElement) => <div className={'shadow'}>{story()}</div>],
excludeStories: /.*Data$/
}
let {title, courseId, image, subtitle, marketing} = courseData
let navigate = action(`navigate to /detail?id=${courseId}`)
const status = <button className={'purchase'}>立即购买</button>
export const Default = () => {
title = text('title', title)
subtitle = text('subtitle', subtitle)
image = text('image', image)
courseId = number('courseId', courseId)
marketing = text('marketing', marketing)
return <CourseCardH title={title} status={status} courseId={courseId} subtitle={subtitle} image={image}
navigate={navigate} marketing={marketing}></CourseCardH>
}
export const V = () => {
title = text('title', title)
image = text('image', image)
courseId = number('courseId', courseId)
return <CourseCardV image={image} courseId={courseId} status={status} title={title}
navigate={navigate} marketing={marketing}></CourseCardV>
}
\ No newline at end of file
.marketing {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 24px;
color: #fff;
font-size: 13px;
background: rgba(224, 46, 36, .6);
text-align: center;
line-height: 24px;
}
\ No newline at end of file
import React from "react";
import { History } from "history";
import { RequireAtLeastOne } from 'src/utils/types'
import CourseCardV from "./course-card-v";
import CourseCardH from "./course-card-h";
interface BaseNavigation {
e: React.MouseEvent
courseId: number
navigate?: (courseId: number) => void
history?: History
}
export type Navigation = RequireAtLeastOne<BaseNavigation, 'history' | 'navigate'>
export const handleNavigation: (navigationArgs: Navigation) => void = ({ e, courseId, navigate, history }) => {
const _n = navigate || function (courseId: number) {
history!.push(`/detail?id=${courseId}`)
}
let nodeName = (e.target as HTMLElement).nodeName.toLowerCase()
if (nodeName === 'a' || nodeName === 'button') {
return
}
_n(courseId)
}
export const H = CourseCardH
export const V = CourseCardV
export default CourseCardH
\ No newline at end of file
$red: #FF2121;
$gray: #999;
.prices {
display: flex;
align-items: center;
.current {
margin-right: 14px;
font-size: 15px;
color: $red;
}
.origin {
font-size: 11px;
text-decoration: line-through;
color: $gray;
}
.sign {
font-size: 11px;
}
}
\ No newline at end of file
import React from "react";
import './index.scss'
interface Props {
current: number | string
origin: number | string
}
const Prices: React.FC<Props> = ({current, origin}) => {
return <div className="prices">
<span className="current"><span className={'sign'}>¥</span>{current}</span>
<span className='origin'>¥{origin}</span>
</div>
}
export default Prices
\ No newline at end of file
import React from 'react'
import Prices from './index'
import {withKnobs, number} from '@storybook/addon-knobs'
export default {
title: 'prices',
component: Prices,
decorators: [withKnobs, (story: () => React.ReactNode) => <div className={'shadow'} style={{width: '100px', padding: '10px'}}>{story()}</div>]
}
export const Default = () => {
const current = number('current', 100)
const origin = number('origin', 1000)
return <Prices current={current} origin={origin}></Prices>
}
\ No newline at end of file
import React, { Component } from 'react';
import './index.scss';
import { HeaderBar, VList } from '../../common'
import { http, dateCountDown } from "src/utils";
import { http } from "src/utils";
import { Link } from 'react-router-dom'
import { Toast } from 'antd-mobile'
import { connect } from "react-redux"
......
import React, {Component} from 'react';
import {V} from 'src/common/course-card'
import {withRouter, RouteComponentProps} from 'react-router-dom'
class TSTest extends Component<RouteComponentProps> {
componentDidMount() {
}
render() {
return (
<div className={'ts-test'}>
TS
<V title={'a'} status={2} courseId={2}
history={this.props.history}
image={'https://julyedu-img-public.oss-cn-beijing.aliyuncs.com/Public/Image/20a86c1353.jpg'}></V>
</div>
);
}
}
export default withRouter(TSTest);
\ No newline at end of file
......@@ -351,4 +351,9 @@ export default [
path: '/anniversary_2020',
component: loadable(() => import('src/components/activity/2020-717')),
},
{
path: '/ts-test',
component: loadable(() => import(/* webpackChunkName: 'ts-test' */'src/components/ts-test'))
}
]
import jsCookie from "js-cookie";
import {
differenceInDays,
differenceInHours,
differenceInMinutes,
differenceInSeconds,
} from 'date-fns'
export const getParam = (key, str) => {
const _s = str ? str : location.href;
const re = new RegExp(`(?:\\?|#|&)(${key})=([^=&#\\?]+)`, 'ig');
......@@ -106,23 +97,6 @@ const browser = (function () {
}
})()
const isLogin = (function () {
return jsCookie.get('uid') && jsCookie.get('token')
})()
const dateCountDown = (later, earlier) => {
const d = differenceInDays(later, earlier)
const h = differenceInHours(later, earlier) % 24
const m = differenceInMinutes(later, earlier) % 60
const s = differenceInSeconds(later, earlier) % 60
return {
d,
h,
m,
s,
}
}
const isValidUrl = (str) => {
return /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/.test(str)
}
......@@ -130,13 +104,10 @@ const isValidUrl = (str) => {
export {
default as http,
}
from './http'
} from './http.ts'
export {
default as wxShare,
}
from './wechat/share'
export { getWXObject } from './wechat/base'
} from './wechat/share'
export {
html,
initCaptcha,
......@@ -144,8 +115,6 @@ export {
validateTel,
validateEmail,
browser,
isLogin,
dateCountDown,
isValidUrl,
loadScript,
getTimestamp,
......
export type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
}[Keys]
export type RequireOnlyOne<T, Keys extends keyof T> = Pick<T, Exclude<keyof T, Keys>> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, undefined>>
}[Keys]
import React from 'react';
import { linkTo } from '@storybook/addon-links';
import { Welcome } from '@storybook/react/demo';
export default {
title: 'Welcome',
component: Welcome,
};
export const ToStorybook = () => <Welcome showApp={linkTo('Button')} />;
ToStorybook.story = {
name: 'to Storybook',
};
import React from 'react';
import { action } from '@storybook/addon-actions';
import { Button } from '@storybook/react/demo';
export default {
title: 'Button',
component: Button,
};
export const Text = () => <Button onClick={action('clicked')}>Hello Button</Button>;
export const Emoji = () => (
<Button onClick={action('clicked')}>
<span role="img" aria-label="so cool">
😀 😎 👍 💯
</span>
</Button>
);
Emoji.story = {
name: 'with emoji',
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment