feat: initial

This commit is contained in:
unix
2020-03-19 01:15:58 +08:00
parent a37c630d54
commit 1a258b23d0
181 changed files with 17112 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
import React from 'react'
export interface FieldItem {
value: string
label: string
}
export interface FieldsetConfig {
register: (item: FieldItem) => void
currentValue: string
inGroup: boolean
}
const defaultContext = {
inGroup: false,
currentValue: '',
register: () => {},
}
export const FieldsetContext = React.createContext<FieldsetConfig>(defaultContext)
export const useFieldset = (): FieldsetConfig => React.useContext<FieldsetConfig>(FieldsetContext)

View File

@@ -0,0 +1,30 @@
import React from 'react'
import withDefaults from '../utils/with-defaults'
interface Props {
className?: string
}
const defaultProps = {
className: ''
}
export type FieldsetFooterActionsProps = Props & typeof defaultProps & React.HTMLAttributes<any>
const FieldsetFooterActions: React.FC<FieldsetFooterActionsProps> = React.memo(({
className, children, ...props
}) => {
return (
<>
<div className={className} {...props}>{children}</div>
<style jsx>{`
div {
display: flex;
justify-content: flex-end;
}
`}</style>
</>
)
})
export default withDefaults(FieldsetFooterActions, defaultProps)

View File

@@ -0,0 +1,37 @@
import React from 'react'
import withDefaults from '../utils/with-defaults'
interface Props {
className?: string
}
const defaultProps = {
className: ''
}
export type FieldsetFooterStatusProps = Props & typeof defaultProps & React.HTMLAttributes<any>
const FieldsetFooterStatus: React.FC<FieldsetFooterStatusProps> = React.memo(({
className, children, ...props
}) => {
return (
<>
<div className={className} {...props}>{children}</div>
<style jsx>{`
div {
font-size: .875rem;
line-height: 1.2;
margin: 0;
display: inline-flex;
word-break: break-word;
}
div>:global(p) {
margin: 0;
}
`}</style>
</>
)
})
export default withDefaults(FieldsetFooterStatus, defaultProps)

View File

@@ -0,0 +1,53 @@
import React from 'react'
import useTheme from '../styles/use-theme'
import FieldsetFooterStatus from './fieldset-footer-status'
import FieldsetFooterActions from './fieldset-footer-actions'
interface Props {
className?: string
}
const defaultProps = {
className: '',
}
export type FieldsetFooterProps = Props & typeof defaultProps & React.HTMLAttributes<any>
const FieldsetFooter: React.FC<React.PropsWithChildren<FieldsetFooterProps>> = React.memo(({
className, children, ...props
}) => {
const theme = useTheme()
return (
<footer className={className} {...props}>
{children}
<style jsx>{`
footer {
background-color: ${theme.palette.accents_1};
border-top: 1px solid ${theme.palette.border};
border-bottom-left-radius: ${theme.layout.radius};
border-bottom-right-radius: ${theme.layout.radius};
display: flex;
justify-content: space-between;
align-items: center;
overflow: hidden;
color: ${theme.palette.accents_6};
padding: ${theme.layout.gapHalf} ${theme.layout.gap};
font-size: .875rem;
box-sizing: border-box;
}
`}</style>
</footer>
)
})
FieldsetFooter.defaultProps = defaultProps
type FieldsetFooterComponent<P = {}> = React.FC<P> & {
Status: typeof FieldsetFooterStatus
Actions: typeof FieldsetFooterActions
}
type ComponentProps = Partial<typeof defaultProps> & Omit<Props, keyof typeof defaultProps>
export default FieldsetFooter as FieldsetFooterComponent<ComponentProps>

View File

@@ -0,0 +1,121 @@
import React, { useCallback, useMemo, useState } from 'react'
import useTheme from '../styles/use-theme'
import withDefaults from '../utils/with-defaults'
import useCurrentState from '../utils/use-current-state'
import { FieldsetContext, FieldItem } from './fieldset-context'
interface Props {
value: string
className?: string
onChange?: (value: string) => void
}
const defaultProps = {
className: '',
}
export type FieldsetGroupProps = Props & typeof defaultProps & React.HTMLAttributes<any>
const FieldsetGroup: React.FC<React.PropsWithChildren<FieldsetGroupProps>> = React.memo(({
className, children, value, onChange, ...props
}) => {
const theme = useTheme()
const [selfVal, setSelfVal] = useState<string>(value)
const [items, setItems, ref] = useCurrentState<FieldItem[]>([])
const register = useCallback((newItem: FieldItem) => {
const hasItem = ref.current.find(item => item.value === newItem.value)
if (hasItem) {
console.error('[Fieldset Group]: The "value" of each "Fieldset" must be unique.')
}
setItems([...ref.current, newItem])
}, [])
const providerValue = useMemo(() => ({
currentValue: selfVal,
inGroup: true,
register: () => register,
}), [selfVal])
const clickHandle = useCallback((nextValue: string) => {
setSelfVal(nextValue)
onChange && onChange(nextValue)
}, [onChange])
return (
<FieldsetContext.Provider value={providerValue}>
<div className={` ${className}`} {...props}>
<div className="group">
{items.map(item => (
<button onClick={() => clickHandle(item.value)}
key={item.value}
className={selfVal === item.value ? 'active' : ''}>
{item.label}
</button>
))}
</div>
<div className="group-content">{children}</div>
<style jsx>{`
.group {
white-space: nowrap;
overflow-y: hidden;
overflow-x: auto;
margin-bottom: -1px;
}
.group-content {
border-top-left-radius: 0;
overflow: hidden;
}
.group-content :global(.fieldset) {
border-top-left-radius: 0;
}
button {
height: 34px;
text-align: center;
user-select: none;
color: ${theme.palette.accents_3};
background-color: ${theme.palette.accents_1};
font-size: .875rem;
white-space: nowrap;
text-transform: capitalize;
line-height: 0;
-webkit-appearance: none;
cursor: pointer;
margin: 0;
padding: 0 ${theme.layout.gap};
overflow: hidden;
transition: all 0.2s ease 0s;
border-radius: 0;
border: 1px solid ${theme.palette.border};
text-decoration: none;
outline: none;
}
button.active {
border-bottom-color: transparent;
background-color: ${theme.palette.background};
color: ${theme.palette.foreground};
cursor: default;
}
button:first-of-type {
border-top-left-radius: ${theme.layout.radius};
}
button:last-of-type {
border-top-right-radius: ${theme.layout.radius};
}
button+button {
border-left: 0;
}
`}</style>
</div>
</FieldsetContext.Provider>
)
})
export default withDefaults(FieldsetGroup, defaultProps)

View File

@@ -0,0 +1,34 @@
import React from 'react'
import useTheme from '../styles/use-theme'
import withDefaults from '../utils/with-defaults'
interface Props {
className?: string
}
const defaultProps = {
className: ''
}
export type FieldsetSubtitleProps = Props & typeof defaultProps & React.HTMLAttributes<HTMLHeadingElement>
const FieldsetSubtitle: React.FC<FieldsetSubtitleProps> = React.memo(({
className, children, ...props
}) => {
const theme = useTheme()
return (
<>
<p className={className} {...props}>{children}</p>
<style jsx>{`
p {
font-size: .875rem;
line-height: 1.6;
margin: ${theme.layout.gapHalf} 0;
}
`}</style>
</>
)
})
export default withDefaults(FieldsetSubtitle, defaultProps)

View File

@@ -0,0 +1,33 @@
import React from 'react'
import withDefaults from '../utils/with-defaults'
interface Props {
className?: string
}
const defaultProps = {
className: ''
}
export type FieldsetTitleProps = Props & typeof defaultProps & React.HTMLAttributes<HTMLHeadingElement>
const FieldsetTitle: React.FC<FieldsetTitleProps> = React.memo(({
className, children, ...props
}) => {
return (
<>
<h4 className={className} {...props}>{children}</h4>
<style jsx>{`
h4 {
font-size: 1.25rem;
line-height: 1.5;
margin: 0;
display: inline-flex;
word-break: break-word;
}
`}</style>
</>
)
})
export default withDefaults(FieldsetTitle, defaultProps)

View File

@@ -0,0 +1,95 @@
import React, { ReactNode, useEffect, useState } from 'react'
import useTheme from '../styles/use-theme'
import FieldsetTitle from './fieldset-title'
import FieldsetSubtitle from './fieldset-subtitle'
import FieldsetFooter from './fieldset-footer'
import FieldsetGroup from './fieldset-group'
import { hasChild, pickChild } from '../utils/collections'
import { useFieldset } from './fieldset-context'
interface Props {
value?: string
label?: string
disabled?: boolean
title?: string | ReactNode
subtitle?: string | ReactNode
className?: string
}
const defaultProps = {
value: '',
label: '',
disabled: false,
title: '',
subtitle: '',
className: '',
}
export type FieldsetProps = Props & typeof defaultProps & React.FieldsetHTMLAttributes<any>
const Fieldset: React.FC<React.PropsWithChildren<FieldsetProps>> = React.memo(({
className, title, subtitle, children, value, label,
}) => {
const theme = useTheme()
const { inGroup, currentValue, register } = useFieldset()
const [hidden, setHidden] = useState<boolean>(inGroup)
const hasTitle = hasChild(children, FieldsetTitle)
const hasSubtitle = hasChild(children, FieldsetSubtitle)
const [withoutFooterChildren, FooterChildren] = pickChild(children, FieldsetFooter)
if (inGroup) {
if (!label) {
console.error('[Fieldset Group]: Props "label" is required when in a group.')
}
if (!value || value === '') {
value = label
}
useEffect(() => {
const r: any = register({ value, label })
r({ value, label })
}, [])
useEffect(() => {
if (!currentValue || currentValue === '') return
setHidden(currentValue !== value)
}, [currentValue])
}
return (
<div className={`fieldset ${className}`}>
<div className="content">
{withoutFooterChildren}
{!hasTitle && <FieldsetTitle>{title}</FieldsetTitle>}
{!hasSubtitle && <FieldsetSubtitle>{subtitle}</FieldsetSubtitle>}
</div>
{FooterChildren && <>{FooterChildren}</>}
<style jsx>{`
.fieldset {
background-color: ${theme.palette.background};
border: 1px solid ${theme.palette.border};
border-radius: ${theme.layout.radius};
overflow: hidden;
display: ${hidden ? 'none' : 'block'};
}
.content {
padding: ${theme.layout.gap};
}
`}</style>
</div>
)
})
Fieldset.defaultProps = defaultProps
type FieldsetComponent<P = {}> = React.FC<P> & {
Title: typeof FieldsetTitle
Subtitle: typeof FieldsetSubtitle
Footer: typeof FieldsetFooter
Group: typeof FieldsetGroup
}
type ComponentProps = Partial<typeof defaultProps> & Omit<Props, keyof typeof defaultProps>
export default Fieldset as FieldsetComponent<ComponentProps>

View File

@@ -0,0 +1,16 @@
import Fieldset from './fieldset'
import FieldsetTitle from './fieldset-title'
import FieldsetSubtitle from './fieldset-subtitle'
import FieldsetFooter from './fieldset-footer'
import FieldsetFooterStatus from './fieldset-footer-status'
import FieldsetFooterActions from './fieldset-footer-actions'
import FieldsetGroup from './fieldset-group'
FieldsetFooter.Status = FieldsetFooterStatus
FieldsetFooter.Actions = FieldsetFooterActions
Fieldset.Title = FieldsetTitle
Fieldset.Subtitle = FieldsetSubtitle
Fieldset.Footer = FieldsetFooter
Fieldset.Group = FieldsetGroup
export default Fieldset