feat: first
This commit is contained in:
8
src/components/Breakpoint/Breakpoint.tsx
Normal file
8
src/components/Breakpoint/Breakpoint.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React, { FC, PropsWithChildren } from 'react'
|
||||
import { BreakpointProps, useBreakpoint } from './useBreakpoint'
|
||||
|
||||
export const Breakpoint: FC<PropsWithChildren<BreakpointProps>> = ({ children, ...props }) => {
|
||||
const enabled = useBreakpoint(props)
|
||||
|
||||
return enabled ? <>{children}</> : null
|
||||
}
|
||||
25
src/components/Breakpoint/BreakpointProvider.tsx
Normal file
25
src/components/Breakpoint/BreakpointProvider.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React, { FC, PropsWithChildren } from 'react'
|
||||
import { Size, useSize } from './useSize'
|
||||
|
||||
export const breakpointList = {
|
||||
xs: 0,
|
||||
s: 576,
|
||||
m: 768,
|
||||
l: 992,
|
||||
xl: 1200,
|
||||
}
|
||||
|
||||
export interface BreakpointContextProps {
|
||||
size: Size,
|
||||
breakpointList: typeof breakpointList
|
||||
}
|
||||
|
||||
export const BreakpointContext = React.createContext<BreakpointContextProps>({ breakpointList, size: {} } as any)
|
||||
|
||||
|
||||
export const BreakpointProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const size = useSize()
|
||||
return (
|
||||
<BreakpointContext.Provider value={{ size, breakpointList }}>{children}</BreakpointContext.Provider>
|
||||
)
|
||||
}
|
||||
3
src/components/Breakpoint/index.ts
Normal file
3
src/components/Breakpoint/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { BreakpointProvider } from './BreakpointProvider'
|
||||
export { useBreakpoint } from './useBreakpoint'
|
||||
export { Breakpoint } from './Breakpoint'
|
||||
81
src/components/Breakpoint/useBreakpoint.ts
Normal file
81
src/components/Breakpoint/useBreakpoint.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { useContext } from 'react';
|
||||
import { BreakpointContext } from './BreakpointProvider'
|
||||
import { uaParser } from '../../utils/uaParser'
|
||||
|
||||
export interface DeviceType {
|
||||
mobile?: boolean;
|
||||
tablet?: boolean;
|
||||
desktop?: boolean;
|
||||
}
|
||||
|
||||
export interface OrientationType {
|
||||
landscape?: boolean
|
||||
portrait?: boolean
|
||||
}
|
||||
|
||||
export interface Breakpoints {
|
||||
xs?: boolean
|
||||
s?: boolean
|
||||
m?: boolean
|
||||
l?: boolean
|
||||
xl?: boolean
|
||||
// greater than the width
|
||||
up?: boolean
|
||||
// less than the width
|
||||
down?: boolean
|
||||
width?: {
|
||||
min?: number
|
||||
max?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type BreakpointProps = DeviceType & Breakpoints & OrientationType
|
||||
|
||||
const deviceType = ['mobile', 'tablet', 'desktop']
|
||||
|
||||
const currentDeviceType = ['mobile', 'tablet'].includes(uaParser.device.model || '') ? uaParser.device.model : 'desktop'
|
||||
|
||||
function matchWidth(width: number, min?: number, max?: number): boolean {
|
||||
if (typeof min === 'number' && typeof max === 'number') {
|
||||
return width >= min && width <= max
|
||||
}
|
||||
if (typeof min === 'number') {
|
||||
return width >= min
|
||||
}
|
||||
if (typeof max === 'number') {
|
||||
return width <= max
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export const useBreakpoint = (props: BreakpointProps): boolean => {
|
||||
const { breakpointList, size: { orientation, ...size } } = useContext(BreakpointContext)
|
||||
let enabled: boolean
|
||||
|
||||
const allKeys = Object.keys(props)
|
||||
|
||||
const keysWithoutDeviceType = allKeys.filter(key => !deviceType.includes(key))
|
||||
const keysWithDeviceType = allKeys.filter(key => deviceType.includes(key))
|
||||
|
||||
enabled = keysWithoutDeviceType.every(key => {
|
||||
if (typeof breakpointList[key as keyof typeof breakpointList] === 'number' && !props.width) {
|
||||
if (props.up) {
|
||||
return size.width > breakpointList[key as keyof typeof breakpointList]
|
||||
}
|
||||
if (props.down) {
|
||||
return size.width <= breakpointList[key as keyof typeof breakpointList]
|
||||
}
|
||||
return size.width === breakpointList[key as keyof typeof breakpointList]
|
||||
}
|
||||
if (key === 'width') {
|
||||
return matchWidth(3000, props.width?.min, props.width?.max)
|
||||
}
|
||||
if (key === 'landscape' || key === 'portrait') {
|
||||
return key === orientation
|
||||
}
|
||||
|
||||
return true
|
||||
}) && (keysWithDeviceType.length ? keysWithDeviceType.some(key => currentDeviceType === key) : true)
|
||||
|
||||
return enabled
|
||||
}
|
||||
56
src/components/Breakpoint/useSize.ts
Normal file
56
src/components/Breakpoint/useSize.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { useCallback, useLayoutEffect, useState } from 'react'
|
||||
|
||||
type Orientation = "portrait" | "landscape"
|
||||
|
||||
function getOrientation(): Orientation {
|
||||
if (
|
||||
window.screen.orientation &&
|
||||
Object.prototype.hasOwnProperty.call(window, 'onorientationchange')
|
||||
) {
|
||||
return window.screen.orientation.type.includes('portrait') ? 'portrait' : 'landscape'
|
||||
}
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(window, 'orientation')
|
||||
) {
|
||||
return Math.abs(window.orientation) !== 90 ? 'portrait' : 'landscape'
|
||||
|
||||
}
|
||||
return window.innerHeight / window.innerWidth > 1 ? "portrait" : "landscape"
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
width: number
|
||||
height: number
|
||||
orientation: Orientation
|
||||
}
|
||||
|
||||
export function useSize():Size {
|
||||
const [width, setWidth] = useState<number>(document.documentElement.clientWidth)
|
||||
const [height, setHeight] = useState<number>(document.documentElement.clientHeight)
|
||||
const [orientation, setOrientation] = useState<Orientation>(getOrientation())
|
||||
|
||||
const resizeListener = useCallback(() => {
|
||||
setWidth(document.documentElement.clientWidth)
|
||||
setHeight(document.documentElement.clientHeight)
|
||||
}, [])
|
||||
|
||||
const orientationListener =useCallback( () => {
|
||||
setOrientation(getOrientation())
|
||||
},[])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
window.addEventListener('resize', resizeListener)
|
||||
window.addEventListener('orientationchange', orientationListener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resizeListener)
|
||||
window.removeEventListener('orientationchange', orientationListener)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
orientation
|
||||
}
|
||||
}
|
||||
9
src/components/Button/Button.tsx
Normal file
9
src/components/Button/Button.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Button as AntdButton, ButtonProps as AntdButtonProps } from 'antd'
|
||||
|
||||
export type ButtonProps = Omit<AntdButtonProps, 'danger' | 'ghost' | 'shape' | 'size' | 'type'> & {
|
||||
type?: 'primary' | 'link' | 'text'
|
||||
}
|
||||
export const Button: FC<ButtonProps> = (props) => {
|
||||
return <AntdButton type="primary" {...props} />
|
||||
}
|
||||
1
src/components/Button/index.ts
Normal file
1
src/components/Button/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Button'
|
||||
8
src/components/Modal/Modal.tsx
Normal file
8
src/components/Modal/Modal.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Modal as AntdModal, ModalProps as AntdModalProps } from 'antd'
|
||||
import { ReactComponent as CloseIcon } from '../../icons/close.svg'
|
||||
|
||||
export type ModalProps = Omit<AntdModalProps, 'footer' | 'closeIcon' | 'title' | 'okText' | 'okType' | 'okButtonProps' | 'cancelText' | 'cancelButtonProps'>
|
||||
export const Modal: FC<ModalProps> = (props) => {
|
||||
return <AntdModal centered {...props} footer={null} closeIcon={<CloseIcon />} />
|
||||
}
|
||||
1
src/components/Modal/index.ts
Normal file
1
src/components/Modal/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Modal'
|
||||
24
src/components/Steps/StepIcon.tsx
Normal file
24
src/components/Steps/StepIcon.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React, { FC, PropsWithChildren } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const StyledStepIcon = styled.div<{ $active?: boolean }>`
|
||||
background: ${props => props.$active ? '#FFAB83' : '#FFFFFF'};
|
||||
border: 1px solid ${props => props.$active ? '#FFAB83' : '#FF5200'};
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: 8px;
|
||||
font-size: 12px;
|
||||
color: #1E1D1F;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
transition: background-color .3s, border-color .3s;
|
||||
`
|
||||
|
||||
export const StepIcon: FC<PropsWithChildren<{ active: boolean }>> = ({ children, active }) => {
|
||||
return <StyledStepIcon $active={active}>{children}</StyledStepIcon>
|
||||
}
|
||||
43
src/components/Steps/Steps.tsx
Normal file
43
src/components/Steps/Steps.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Steps as AntdSteps, StepsProps as AntdStepsProps } from 'antd'
|
||||
import { StepProps } from 'antd/es/steps'
|
||||
import styled from 'styled-components'
|
||||
import {StepIcon} from './StepIcon'
|
||||
export type StepsProps = Omit<AntdStepsProps, 'labelPlacement' | 'size' | 'status' | 'type' | 'items'> & {
|
||||
items: Omit<StepProps, 'icon' | 'description'>[]
|
||||
}
|
||||
|
||||
const StyledSteps = styled(AntdSteps)`
|
||||
&.ant-steps-label-vertical {
|
||||
.ant-steps-item-content {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.ant-steps-item-tail {
|
||||
top: 8px;
|
||||
margin-inline-start: 53px;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-steps-item-title {
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title {
|
||||
color: #8F9296;
|
||||
}
|
||||
`
|
||||
|
||||
export const Steps: FC<StepsProps> = ({ items, current = 0, ...props }) => {
|
||||
const formattedItems = items.map((item, index) => {
|
||||
return {
|
||||
...item,
|
||||
icon: <StepIcon active={index <= current}>{index + 1}</StepIcon>
|
||||
}
|
||||
})
|
||||
return (
|
||||
<StyledSteps labelPlacement="vertical" {...props} current={current} items={formattedItems} />
|
||||
)
|
||||
}
|
||||
1
src/components/Steps/index.ts
Normal file
1
src/components/Steps/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Steps'
|
||||
21
src/components/Tag/Tag.tsx
Normal file
21
src/components/Tag/Tag.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React, { PropsWithChildren } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {TinyText} from '../Typography'
|
||||
|
||||
const StyledTag = styled.div`
|
||||
background: #FFAB83;
|
||||
border-radius: 7px;
|
||||
text-align: center;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
display: inline-block;
|
||||
|
||||
> span {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
top: -4px;
|
||||
}
|
||||
`
|
||||
export const Tag: React.FC<PropsWithChildren> = (props) => {
|
||||
return <StyledTag><TinyText>{props.children}</TinyText></StyledTag>
|
||||
}
|
||||
1
src/components/Tag/index.ts
Normal file
1
src/components/Tag/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Tag'
|
||||
33
src/components/Tooltip/Tooltip.tsx
Normal file
33
src/components/Tooltip/Tooltip.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React, { FC, useCallback } from 'react'
|
||||
import { Tooltip as AntdTooltip, TooltipProps as AntdTooltipProps } from 'antd'
|
||||
import { ReactComponent as CloseIcon } from '../../icons/close.svg'
|
||||
import styled from 'styled-components'
|
||||
|
||||
export type TooltipProps = AntdTooltipProps & { closeable?: boolean; primary?: boolean }
|
||||
|
||||
const Close = styled.span`
|
||||
line-height: 17px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
padding-left: 4px;
|
||||
|
||||
> svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
line-height: 17px;
|
||||
}
|
||||
`
|
||||
|
||||
export const Tooltip: FC<TooltipProps> = ({ closeable, primary, title, overlayClassName, ...props }) => {
|
||||
const handleClick = useCallback(() => {
|
||||
document.documentElement.click()
|
||||
}, [])
|
||||
return (
|
||||
<AntdTooltip
|
||||
trigger="click"
|
||||
overlayClassName={`health-tooltip${primary ? '-highlighted' : ''} ${overlayClassName || ''}`}
|
||||
{...props}
|
||||
title={<>{title} {closeable ? <Close><CloseIcon onClick={handleClick} /></Close> : null}</>}
|
||||
/>
|
||||
)
|
||||
}
|
||||
1
src/components/Tooltip/index.ts
Normal file
1
src/components/Tooltip/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Tooltip'
|
||||
22
src/components/Typography/ButtonText.tsx
Normal file
22
src/components/Typography/ButtonText.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React, { FC, ComponentProps } from 'react'
|
||||
import { Typography, TypographyProps } from 'antd'
|
||||
import styled from 'styled-components'
|
||||
|
||||
type ButtonTextProps = ComponentProps<TypographyProps['Text']> & {
|
||||
level?: 1 | 2
|
||||
}
|
||||
|
||||
const StyledButtonText = styled(Typography.Text)<{ $level: 1 | 2 }>`
|
||||
${props => props.$level === 1 ? `
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
` : `
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
`}
|
||||
`
|
||||
export const ButtonText: FC<ButtonTextProps> = ({ level = 1, ...props }) => {
|
||||
return <StyledButtonText {...props} $level={level} />
|
||||
}
|
||||
17
src/components/Typography/Paragraph.tsx
Normal file
17
src/components/Typography/Paragraph.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React, { FC, ComponentProps } from 'react'
|
||||
import { Typography, TypographyProps } from 'antd'
|
||||
import styled from 'styled-components'
|
||||
|
||||
type AdditionalParagraphProps = ComponentProps<TypographyProps['Paragraph']> & {
|
||||
level?: 1 | 2
|
||||
}
|
||||
|
||||
const StyledParagraph2 = styled(Typography.Paragraph)`
|
||||
font-size: 12px;
|
||||
`
|
||||
export const Paragraph: FC<AdditionalParagraphProps> = ({ level = 1, ...props }) => {
|
||||
if (level === 2) {
|
||||
return <StyledParagraph2 {...props} />
|
||||
}
|
||||
return <Typography.Paragraph {...props} />
|
||||
}
|
||||
16
src/components/Typography/TinyText.tsx
Normal file
16
src/components/Typography/TinyText.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React, { FC, ComponentProps } from 'react'
|
||||
import { Typography, TypographyProps } from 'antd'
|
||||
import styled from 'styled-components'
|
||||
|
||||
type TinyTextProps = ComponentProps<TypographyProps['Text']>
|
||||
|
||||
const StyledTinyText = styled(Typography.Text)`
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
transform: scale(0.66667);
|
||||
line-height: 1.75;
|
||||
color: #1E1D1F;
|
||||
`
|
||||
export const TinyText: FC<TinyTextProps> = (props) => {
|
||||
return <StyledTinyText {...props} />
|
||||
}
|
||||
8
src/components/Typography/index.ts
Normal file
8
src/components/Typography/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Typography } from 'antd'
|
||||
|
||||
const { Text, Title, Link } = Typography
|
||||
export * from './Paragraph'
|
||||
export * from './ButtonText'
|
||||
export * from './TinyText'
|
||||
export { Text, Title, Link }
|
||||
|
||||
Reference in New Issue
Block a user