feat: export all types related to components (#562)

* feat: export all types related to components

fix(tooltip): fix the vertical offset of the arrow

* refactor: optimize events of all popup related components

* test: append testcases for popup base component

* test: add testcase for visible events

* test: update snapshots
This commit is contained in:
witt
2021-06-23 23:05:59 +08:00
committed by unix
parent 7facec3849
commit de0c8fee97
117 changed files with 1062 additions and 586 deletions

View File

@@ -18,6 +18,8 @@ import { pickChild } from '../utils/collections'
import useCurrentState from '../utils/use-current-state'
import useScaleable, { filterScaleableProps, withScaleable } from '../use-scaleable'
export type AutoCompleteTypes = NormalTypes
export type AutoCompleteOption = {
label: string
value: string
@@ -29,7 +31,7 @@ export type AutoCompleteOptions = Array<
interface Props {
options?: AutoCompleteOptions
type?: NormalTypes
type?: AutoCompleteTypes
initialValue?: string
value?: string
onChange?: (value: string) => void
@@ -50,7 +52,7 @@ const defaultProps = {
initialValue: '',
disabled: false,
clearable: false,
type: 'default' as NormalTypes,
type: 'default' as AutoCompleteTypes,
disableMatchWidth: false,
disableFreeSolo: false,
className: '',
@@ -135,10 +137,10 @@ const AutoCompleteComponent = React.forwardRef<
}
return childrenToOptionsNode(options as Array<AutoCompleteOption>)
}, [searching, options])
const showClearIcon = useMemo(() => clearable && searching === undefined, [
clearable,
searching,
])
const showClearIcon = useMemo(
() => clearable && searching === undefined,
[clearable, searching],
)
const updateValue = (val: string) => {
if (disabled) return

View File

@@ -14,4 +14,10 @@ export type AutoCompleteComponentType = typeof AutoComplete & {
;(AutoComplete as AutoCompleteComponentType).Searching = AutoCompleteSearching
;(AutoComplete as AutoCompleteComponentType).Empty = AutoCompleteEmpty
export type {
AutoCompleteOption,
AutoCompleteOptions,
AutoCompleteProps,
AutoCompleteTypes,
} from './auto-complete'
export default AutoComplete as AutoCompleteComponentType

View File

@@ -6,4 +6,7 @@ export type AvatarComponentType = typeof Avatar & {
}
;(Avatar as AvatarComponentType).Group = AvatarGroup
export type { AvatarProps } from './avatar'
export type { AvatarGroupProps } from './avatar-group'
export default Avatar as AvatarComponentType

View File

@@ -5,7 +5,7 @@ import Badge from './badge'
const placement = tuple('topLeft', 'topRight', 'bottomLeft', 'bottomRight')
type BadgeAnchorPlacement = typeof placement[number]
export type BadgeAnchorPlacement = typeof placement[number]
interface Props {
placement?: BadgeAnchorPlacement

View File

@@ -4,14 +4,16 @@ import { NormalTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from '../themes/presets'
import useScaleable, { withScaleable } from '../use-scaleable'
export type BadgeTypes = NormalTypes
interface Props {
type?: NormalTypes
type?: BadgeTypes
dot?: boolean
className?: string
}
const defaultProps = {
type: 'default' as NormalTypes,
type: 'default' as BadgeTypes,
dot: false,
className: '',
}

View File

@@ -6,4 +6,6 @@ export type BadgeComponentType = typeof Badge & {
}
;(Badge as BadgeComponentType).Anchor = BadgeAnchor
export type { BadgeProps, BadgeTypes } from './badge'
export type { BadgeAnchorProps, BadgeAnchorPlacement } from './badge-anchor'
export default Badge as BadgeComponentType

View File

@@ -9,4 +9,7 @@ export type BreadcrumbsComponentType = typeof Breadcrumbs & {
;(Breadcrumbs as BreadcrumbsComponentType).Item = BreadcrumbsItem
;(Breadcrumbs as BreadcrumbsComponentType).Separator = BreadcrumbsSeparator
export type { BreadcrumbsProps } from './breadcrumbs'
export type { BreadcrumbsItemProps } from './breadcrumbs-item'
export type { BreadcrumbsSeparatorProps } from './breadcrumbs-separator'
export default Breadcrumbs as BreadcrumbsComponentType

View File

@@ -5,16 +5,18 @@ import { useButtonDropdown } from './button-dropdown-context'
import Loading from '../loading'
import { NormalTypes } from '../utils/prop-types'
export type ButtonDropdownItemTypes = NormalTypes
interface Props {
main?: boolean
type?: NormalTypes
type?: ButtonDropdownItemTypes
onClick?: React.MouseEventHandler<HTMLElement>
className?: string
}
const defaultProps = {
main: false,
type: 'default' as NormalTypes,
type: 'default' as ButtonDropdownItemTypes,
onClick: () => {},
className: '',
}

View File

@@ -9,8 +9,10 @@ import { NormalTypes } from '../utils/prop-types'
import { pickChild, pickChildByProps } from '../utils/collections'
import useScaleable, { withScaleable } from '../use-scaleable'
export type ButtonDropdownTypes = NormalTypes
interface Props {
type?: NormalTypes
type?: ButtonDropdownTypes
auto?: boolean
loading?: boolean
disabled?: boolean
@@ -18,7 +20,7 @@ interface Props {
}
const defaultProps = {
type: 'default' as NormalTypes,
type: 'default' as ButtonDropdownTypes,
auto: false,
loading: false,
disabled: false,

View File

@@ -6,4 +6,9 @@ type ButtonDropdownType = typeof ButtonDropdown & {
}
;(ButtonDropdown as ButtonDropdownType).Item = ButtonDropdownItem
export type { ButtonDropdownProps, ButtonDropdownTypes } from './button-dropdown'
export type {
ButtonDropdownItemProps,
ButtonDropdownItemTypes,
} from './button-dropdown-item'
export default ButtonDropdown as ButtonDropdownType

View File

@@ -1,3 +1,5 @@
import ButtonGroup from './button-group'
export type { ButtonGroupProps } from './button-group'
export type { ButtonTypes } from '../utils/prop-types'
export default ButtonGroup

View File

@@ -1,3 +1,5 @@
import Button from './button'
export type { ButtonProps } from './button'
export type { ButtonTypes } from '../utils/prop-types'
export default Button

View File

@@ -1,3 +1,4 @@
import Capacity from './capacity'
export type { CapacityProps } from './capacity'
export default Capacity

View File

@@ -13,4 +13,8 @@ export type CardComponentType = typeof Card & {
;(Card as CardComponentType).Content = CardContent
;(Card as CardComponentType).Body = CardContent
export type { CardProps } from './card'
export type { CardContentProps } from './card-content'
export type { CardFooterProps } from './card-footer'
export type { CardTypes } from '../utils/prop-types'
export default Card as CardComponentType

View File

@@ -7,10 +7,10 @@ import { getColors } from './styles'
import useTheme from '../use-theme'
import useScaleable, { withScaleable } from '../use-scaleable'
interface CheckboxEventTarget {
export type CheckboxTypes = NormalTypes
export interface CheckboxEventTarget {
checked: boolean
}
export interface CheckboxEvent {
target: CheckboxEventTarget
stopPropagation: () => void
@@ -21,7 +21,7 @@ export interface CheckboxEvent {
interface Props {
checked?: boolean
disabled?: boolean
type?: NormalTypes
type?: CheckboxTypes
initialChecked?: boolean
onChange?: (e: CheckboxEvent) => void
className?: string
@@ -30,7 +30,7 @@ interface Props {
const defaultProps = {
disabled: false,
type: 'default' as NormalTypes,
type: 'default' as CheckboxTypes,
initialChecked: false,
className: '',
value: '',
@@ -70,10 +70,10 @@ const CheckboxComponent: React.FC<CheckboxProps> = ({
}, [values.join(',')])
}
const { fill, bg } = useMemo(() => getColors(theme.palette, type), [
theme.palette,
type,
])
const { fill, bg } = useMemo(
() => getColors(theme.palette, type),
[theme.palette, type],
)
const changeHandle = useCallback(
(ev: React.ChangeEvent) => {

View File

@@ -6,4 +6,11 @@ export type CheckboxComponentType = typeof Checkbox & {
}
;(Checkbox as CheckboxComponentType).Group = CheckboxGroup
export type {
CheckboxProps,
CheckboxEvent,
CheckboxEventTarget,
CheckboxTypes,
} from './checkbox'
export type { CheckboxGroupProps } from './checkbox-group'
export default Checkbox as CheckboxComponentType

View File

@@ -1,5 +1,4 @@
import Code from './code'
import { CodeProps } from './code'
export type Props = CodeProps
export type { CodeProps } from './code'
export default Code

View File

@@ -1,5 +1,4 @@
import Col from './col'
import { ColProps } from './col'
export type Props = ColProps
export type { ColProps } from './col'
export default Col

View File

@@ -6,4 +6,6 @@ export type CollapseComponentType = typeof Collapse & {
}
;(Collapse as CollapseComponentType).Group = CollapseGroup
export default Collapse
export type { CollapseProps } from './collapse'
export type { CollapseGroupProps } from './collapse-group'
export default Collapse as CollapseComponentType

View File

@@ -1,5 +1,4 @@
import Description from './description'
import { DescriptionProps } from './description'
export type Props = DescriptionProps
export type { DescriptionProps } from './description'
export default Description

View File

@@ -1,5 +1,4 @@
import Display from './display'
import { DisplayProps } from './display'
export type Props = DisplayProps
export type { DisplayProps } from './display'
export default Display

View File

@@ -549,8 +549,8 @@ initialize {
"next": Node {
"attribs": Object {
"class": "divider ",
"h": "2",
"role": "separator",
"volume": "2",
},
"children": Array [
Node {
@@ -579,7 +579,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.125 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -649,7 +649,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.125 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -732,13 +732,13 @@ initialize {
"type": "tag",
"x-attribsNamespace": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"volume": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"volume": undefined,
},
},
"parent": [Circular],
@@ -1138,8 +1138,8 @@ initialize {
"next": Node {
"attribs": Object {
"class": "divider ",
"h": "2",
"role": "separator",
"volume": "2",
},
"children": Array [
Node {
@@ -1168,7 +1168,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.125 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -1238,7 +1238,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.125 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -1321,13 +1321,13 @@ initialize {
"type": "tag",
"x-attribsNamespace": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"volume": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"volume": undefined,
},
},
"parent": [Circular],
@@ -1727,8 +1727,8 @@ initialize {
"next": Node {
"attribs": Object {
"class": "divider ",
"h": "2",
"role": "separator",
"volume": "2",
},
"children": Array [
Node {
@@ -1757,7 +1757,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.125 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -1827,7 +1827,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.125 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -1910,13 +1910,13 @@ initialize {
"type": "tag",
"x-attribsNamespace": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"volume": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"volume": undefined,
},
},
"parent": [Circular],
@@ -2316,8 +2316,8 @@ initialize {
Node {
"attribs": Object {
"class": "divider ",
"h": "2",
"role": "separator",
"volume": "2",
},
"children": Array [
Node {
@@ -2346,7 +2346,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.125 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -2416,7 +2416,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.125 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -3072,13 +3072,13 @@ initialize {
"type": "tag",
"x-attribsNamespace": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"volume": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"volume": undefined,
},
},
],
@@ -3874,7 +3874,7 @@ initialize {
}
`;
exports[`Divider should work with x and y 1`] = `
exports[`Divider should work with w and h 1`] = `
initialize {
"0": Node {
"attribs": Object {},
@@ -3883,7 +3883,7 @@ initialize {
"attribs": Object {
"class": "divider ",
"role": "separator",
"x": "3",
"w": "3",
},
"children": Array [
Node {
@@ -3896,7 +3896,7 @@ initialize {
background-color: #eaeaea;
position: relative;
font-size: calc(1 * 16px);
width: auto;
width: calc(3 * 16px);
height: calc(0.0625 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
@@ -3952,8 +3952,8 @@ initialize {
"next": Node {
"attribs": Object {
"class": "divider ",
"h": "3",
"role": "separator",
"y": "3",
},
"children": Array [
Node {
@@ -3967,7 +3967,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.1875 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -4025,13 +4025,13 @@ initialize {
"type": "tag",
"x-attribsNamespace": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"y": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"y": undefined,
},
},
"parent": [Circular],
@@ -4040,19 +4040,19 @@ initialize {
"x-attribsNamespace": Object {
"class": undefined,
"role": undefined,
"x": undefined,
"w": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
"role": undefined,
"x": undefined,
"w": undefined,
},
},
Node {
"attribs": Object {
"class": "divider ",
"h": "3",
"role": "separator",
"y": "3",
},
"children": Array [
Node {
@@ -4066,7 +4066,7 @@ initialize {
position: relative;
font-size: calc(1 * 16px);
width: auto;
height: calc(0.0625 * 16px);
height: calc(0.1875 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
}
@@ -4124,7 +4124,7 @@ initialize {
"attribs": Object {
"class": "divider ",
"role": "separator",
"x": "3",
"w": "3",
},
"children": Array [
Node {
@@ -4137,7 +4137,7 @@ initialize {
background-color: #eaeaea;
position: relative;
font-size: calc(1 * 16px);
width: auto;
width: calc(3 * 16px);
height: calc(0.0625 * 16px);
padding: 0 0 0 0;
margin: calc(0.5 * 16px) 0 calc(0.5 * 16px) 0;
@@ -4197,24 +4197,24 @@ initialize {
"x-attribsNamespace": Object {
"class": undefined,
"role": undefined,
"x": undefined,
"w": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
"role": undefined,
"x": undefined,
"w": undefined,
},
},
"type": "tag",
"x-attribsNamespace": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"y": undefined,
},
"x-attribsPrefix": Object {
"class": undefined,
"h": undefined,
"role": undefined,
"y": undefined,
},
},
],

View File

@@ -9,11 +9,11 @@ describe('Divider', () => {
expect(() => wrapper.unmount()).not.toThrow()
})
it('should work with x and y', () => {
it('should work with w and h', () => {
const wrapper = render(
<div>
<Divider x={3} />
<Divider y={3} />
<Divider w={3} />
<Divider h={3} />
</div>,
)
expect(wrapper).toMatchSnapshot()
@@ -36,7 +36,7 @@ describe('Divider', () => {
<Divider align="start">start</Divider>
<Divider align="left">left</Divider>
<Divider align="end">end</Divider>
<Divider align="start" volume={2}>
<Divider align="start" h={2}>
start
</Divider>
</div>,
@@ -47,8 +47,8 @@ describe('Divider', () => {
it('should support float', () => {
const wrapper = mount(
<div>
<Divider x={1.1} y={2.5} />
<Divider volume={2.5} />
<Divider w={1.1} h={2.5} />
<Divider h={2.5} />
</div>,
)
expect(wrapper).toMatchSnapshot()

View File

@@ -1,3 +1,5 @@
import Divider from './divider'
export type { DividerProps, DividerTypes } from './divider'
export type { DividerAlign } from '../utils/prop-types'
export default Divider

View File

@@ -4,21 +4,22 @@ import { NormalTypes } from '../utils/prop-types'
import { GeistUIThemes } from '../themes/presets'
import useScaleable, { withScaleable } from '../use-scaleable'
export type DotTypes = NormalTypes
interface Props {
type?: NormalTypes
type?: DotTypes
className?: string
}
const defaultProps = {
type: 'default' as NormalTypes,
type: 'default' as DotTypes,
className: '',
}
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type DotProps = Props & NativeAttrs
const getColor = (type: NormalTypes, theme: GeistUIThemes): string => {
const colors: { [key in NormalTypes]?: string } = {
const getColor = (type: DotTypes, theme: GeistUIThemes): string => {
const colors: { [key in DotTypes]?: string } = {
default: theme.palette.accents_2,
success: theme.palette.success,
warning: theme.palette.warning,

View File

@@ -1,5 +1,4 @@
import Dot from './dot'
import { DotProps } from './dot'
export type Props = DotProps
export type { DotProps, DotTypes } from './dot'
export default Dot

View File

@@ -20,4 +20,10 @@ export type FieldsetComponentType = typeof Fieldset & {
;(Fieldset as FieldsetComponentType).Content = FieldsetContent
;(Fieldset as FieldsetComponentType).Body = FieldsetContent
export type { FieldsetProps } from './fieldset'
export type { FieldsetTitleProps } from './fieldset-title'
export type { FieldsetSubtitleProps } from './fieldset-subtitle'
export type { FieldsetGroupProps } from './fieldset-group'
export type { FieldsetFooterProps } from './fieldset-footer'
export type { FieldsetContentProps } from './fieldset-content'
export default Fieldset as FieldsetComponentType

View File

@@ -9,12 +9,12 @@ import useCurrentState from '../utils/use-current-state'
import ToastContainer, { ToastWithID } from '../use-toasts/toast-container'
import { GeistUIThemes } from '../themes/presets'
export interface Props {
export type GeistProviderProps = {
themes?: Array<GeistUIThemes>
themeType?: string | 'dark' | 'light'
}
const GeistProvider: React.FC<PropsWithChildren<Props>> = ({
const GeistProvider: React.FC<PropsWithChildren<GeistProviderProps>> = ({
themes,
themeType,
children,

View File

@@ -1,3 +1,4 @@
import GeistProvider from './geist-provider'
export type { GeistProviderProps } from './geist-provider'
export default GeistProvider

View File

@@ -1,28 +1,33 @@
import React, { useMemo } from 'react'
import useTheme from '../use-theme'
import { Justify, Direction, AlignItems, AlignContent } from './grid-types'
import {
GridJustify,
GridDirection,
GridAlignItems,
GridAlignContent,
} from './grid-types'
import useScaleable from '../use-scaleable'
type BreakpointsValue = number | boolean
export type GridBreakpointsValue = number | boolean
export interface GridBasicComponentProps {
xs?: BreakpointsValue
sm?: BreakpointsValue
md?: BreakpointsValue
lg?: BreakpointsValue
xl?: BreakpointsValue
justify?: Justify
direction?: Direction
alignItems?: AlignItems
alignContent?: AlignContent
xs?: GridBreakpointsValue
sm?: GridBreakpointsValue
md?: GridBreakpointsValue
lg?: GridBreakpointsValue
xl?: GridBreakpointsValue
justify?: GridJustify
direction?: GridDirection
alignItems?: GridAlignItems
alignContent?: GridAlignContent
className?: string
}
const defaultProps = {
xs: false as BreakpointsValue,
sm: false as BreakpointsValue,
md: false as BreakpointsValue,
lg: false as BreakpointsValue,
xl: false as BreakpointsValue,
xs: false as GridBreakpointsValue,
sm: false as GridBreakpointsValue,
md: false as GridBreakpointsValue,
lg: false as GridBreakpointsValue,
xl: false as GridBreakpointsValue,
className: '',
}
@@ -35,7 +40,7 @@ type ItemLayoutValue = {
basis: string
display: string
}
const getItemLayout = (val: BreakpointsValue): ItemLayoutValue => {
const getItemLayout = (val: GridBreakpointsValue): ItemLayoutValue => {
const display = val === 0 ? 'display: none;' : 'display: inherit;'
if (typeof val === 'number') {
const width = (100 / 24) * val

View File

@@ -1,18 +1,18 @@
import React, { useMemo } from 'react'
import GridBasicItem, { GridBasicItemProps } from './basic-item'
import { Wrap } from './grid-types'
import { GridWrap } from './grid-types'
import { css } from 'styled-jsx/css'
import useScaleable, { withScaleable } from '../use-scaleable'
interface Props {
gap?: number
wrap?: Wrap
wrap?: GridWrap
className?: string
}
const defaultProps = {
gap: 0,
wrap: 'wrap' as Wrap,
wrap: 'wrap' as GridWrap,
className: '',
}

View File

@@ -9,11 +9,11 @@ const justify = tuple(
'space-evenly',
)
export type Justify = typeof justify[number]
export type GridJustify = typeof justify[number]
const alignItems = tuple('flex-start', 'center', 'flex-end', 'stretch', 'baseline')
export type AlignItems = typeof alignItems[number]
export type GridAlignItems = typeof alignItems[number]
const alignContent = tuple(
'stretch',
@@ -24,12 +24,12 @@ const alignContent = tuple(
'space-around',
)
export type AlignContent = typeof alignContent[number]
export type GridAlignContent = typeof alignContent[number]
const direction = tuple('row', 'row-reverse', 'column', 'column-reverse')
export type Direction = typeof direction[number]
export type GridDirection = typeof direction[number]
const wrap = tuple('nowrap', 'wrap', 'wrap-reverse')
export type Wrap = typeof wrap[number]
export type GridWrap = typeof wrap[number]

View File

@@ -6,4 +6,14 @@ export type GridComponentType = typeof Grid & {
}
;(Grid as GridComponentType).Container = GridContainer
export type { GridContainerProps } from './grid-container'
export type { GridProps } from './grid'
export type { GridBreakpointsValue } from './basic-item'
export type {
GridAlignContent,
GridAlignItems,
GridDirection,
GridJustify,
GridWrap,
} from './grid-types'
export default Grid as GridComponentType

View File

@@ -6,21 +6,21 @@ import ImageBrowserHttpsIcon from './image-browser-https-icon'
import { getBrowserColors, BrowserColors } from './styles'
import useScaleable, { withScaleable } from '../use-scaleable'
type AnchorProps = Omit<React.AnchorHTMLAttributes<any>, keyof LinkProps>
export type ImageAnchorProps = Omit<React.AnchorHTMLAttributes<any>, keyof LinkProps>
interface Props {
title?: string
url?: string
showFullLink?: boolean
invert?: boolean
anchorProps?: AnchorProps
anchorProps?: ImageAnchorProps
className?: string
}
const defaultProps = {
className: '',
showFullLink: false,
anchorProps: {} as AnchorProps,
anchorProps: {} as ImageAnchorProps,
invert: false,
}
@@ -51,7 +51,7 @@ const getAddressInput = (
url: string,
showFullLink: boolean,
colors: BrowserColors,
anchorProps: AnchorProps,
anchorProps: ImageAnchorProps,
) => (
<div className="address-input">
<span className="https">
@@ -124,10 +124,10 @@ const ImageBrowserComponent = React.forwardRef<
) => {
const theme = useTheme()
const { SCALES } = useScaleable()
const colors = useMemo(() => getBrowserColors(invert, theme.palette), [
invert,
theme.palette,
])
const colors = useMemo(
() => getBrowserColors(invert, theme.palette),
[invert, theme.palette],
)
const input = useMemo(() => {
if (url) return getAddressInput(url, showFullLink, colors, anchorProps)
if (title) return getTitle(title, colors)

View File

@@ -6,4 +6,6 @@ export type ImageComponentType = typeof Image & {
}
;(Image as ImageComponentType).Browser = ImageBrowser
export type { ImageProps } from './image'
export type { ImageBrowserProps, ImageAnchorProps } from './image-browser'
export default Image as ImageComponentType

View File

@@ -1,61 +1,190 @@
/// <reference types="styled-jsx" />
export * from './themes/presets'
export { default as Themes } from './themes'
export { default as useTheme } from './use-theme'
export { default as useAllThemes } from './use-all-themes'
export { default as AutoComplete } from './auto-complete'
export type { AutoCompleteProps } from './auto-complete'
export { default as Avatar } from './avatar'
export type { AvatarProps, AvatarGroupProps } from './avatar'
export { default as Badge } from './badge'
export type { BadgeProps, BadgeAnchorProps } from './badge'
export { default as Breadcrumbs } from './breadcrumbs'
export type {
BreadcrumbsProps,
BreadcrumbsItemProps,
BreadcrumbsSeparatorProps,
} from './breadcrumbs'
export { default as Button } from './button'
export type { ButtonProps } from './button'
export { default as ButtonDropdown } from './button-dropdown'
export type { ButtonDropdownProps, ButtonDropdownItemProps } from './button-dropdown'
export { default as ButtonGroup } from './button-group'
export type { ButtonGroupProps } from './button-group'
export { default as Capacity } from './capacity'
export type { CapacityProps } from './capacity'
export { default as Card } from './card'
export type { CardProps, CardContentProps, CardFooterProps } from './card'
export { default as Checkbox } from './checkbox'
export type { CheckboxProps, CheckboxGroupProps } from './checkbox'
export { default as Code } from './code'
export type { CodeProps } from './code'
export { default as Collapse } from './collapse'
export type { CollapseProps, CollapseGroupProps } from './collapse'
export { default as Description } from './description'
export type { DescriptionProps } from './description'
export { default as Display } from './display'
export type { DisplayProps } from './display'
export { default as Divider } from './divider'
export type { DividerProps } from './divider'
export { default as Dot } from './dot'
export type { DotProps } from './dot'
export { default as Fieldset } from './fieldset'
export type {
FieldsetProps,
FieldsetTitleProps,
FieldsetSubtitleProps,
FieldsetGroupProps,
FieldsetFooterProps,
FieldsetContentProps,
} from './fieldset'
export { default as GeistProvider } from './geist-provider'
export { default as CssBaseline } from './css-baseline'
export type { GeistProviderProps } from './geist-provider'
export { default as Grid } from './grid'
export type { GridProps, GridContainerProps } from './grid'
export { default as Image } from './image'
export type { ImageProps, ImageBrowserProps } from './image'
export { default as Input } from './input'
export type { InputProps, InputPasswordProps } from './input'
export { default as Keyboard } from './keyboard'
export type { KeyboardProps } from './keyboard'
export { default as Link } from './link'
export type { LinkProps } from './link'
export { default as Loading } from './loading'
export type { LoadingProps } from './loading'
export { default as Modal } from './modal'
export type {
ModalProps,
ModalTitleProps,
ModalSubtitleProps,
ModalContentProps,
ModalActionProps,
} from './modal'
export { default as Note } from './note'
export type { NoteProps } from './note'
export { default as Page } from './page'
export type {
PageProps,
PageHeaderProps,
PageContentProps,
PageFooterProps,
} from './page'
export { default as Pagination } from './pagination'
export type {
PaginationProps,
PaginationNextProps,
PaginationPreviousProps,
} from './pagination'
export { default as Popover } from './popover'
export type { PopoverProps, PopoverItemProps } from './popover'
export { default as Progress } from './progress'
export type { ProgressProps } from './progress'
export { default as Radio } from './radio'
export type { RadioProps, RadioGroupProps, RadioDescriptionProps } from './radio'
export { default as Select } from './select'
export type { SelectProps, SelectOptionProps } from './select'
export { default as Slider } from './slider'
export type { SliderProps } from './slider'
export { default as Snippet } from './snippet'
export type { SnippetProps } from './snippet'
export { default as Spacer } from './spacer'
export type { SpacerProps } from './spacer'
export { default as Spinner } from './spinner'
export type { SpinnerProps } from './spinner'
export { default as Table } from './table'
export type { TableProps, TableColumnProps } from './table'
export { default as Tabs } from './tabs'
export type { TabsProps } from './tabs'
export { default as Tag } from './tag'
export type { TagProps } from './tag'
export { default as Text } from './text'
export type { TextProps } from './text'
export { default as Textarea } from './textarea'
export type { TextareaProps } from './textarea'
export { default as Themes } from './themes'
export type { GeistUIThemes, GeistUserTheme } from './themes'
export { default as Toggle } from './toggle'
export type { ToggleProps } from './toggle'
export { default as Tooltip } from './tooltip'
export type { TooltipProps } from './tooltip'
export { default as Tree } from './tree'
export type { TreeProps } from './tree'
export { default as useAllThemes } from './use-all-themes'
export type { AllThemesConfig } from './use-all-themes'
export { default as useToasts } from './use-toasts'
export type { Toast } from './use-toasts'
export { default as User } from './user'
export type { UserProps } from './user'
export { default as useBodyScroll } from './use-body-scroll'
export type { BodyScrollOptions } from './use-body-scroll'
export { default as useClipboard } from './use-clipboard'
export type { UseClipboardOptions } from './use-clipboard'
export { default as useMediaQuery } from './use-media-query'
export type { ResponsiveOptions, ResponsiveBreakpoint } from './use-media-query'
export { default as useKeyboard, KeyMod, KeyCode } from './use-keyboard'
export type { KeyboardOptions, UseKeyboardHandler } from './use-keyboard'
export { default as useInput } from './input/use-input'
export { default as useModal } from './modal/use-modal'
export { default as useTabs } from './tabs/use-tabs'
export { default as useBodyScroll } from './use-body-scroll'
export { default as useClickAway } from './use-click-away'
export { default as useClipboard } from './use-clipboard'
export { default as useCurrentState } from './use-current-state'
export { default as useMediaQuery } from './use-media-query'
export { default as useKeyboard, KeyMod, KeyCode } from './use-keyboard'
export { default as Avatar } from './avatar'
export { default as Text } from './text'
export { default as Note } from './note'
export { default as Link } from './link'
export { default as Button } from './button'
export { default as Card } from './card'
export { default as Code } from './code'
export { default as Display } from './display'
export { default as Description } from './description'
export { default as Image } from './image'
export { default as Spacer } from './spacer'
export { default as Tag } from './tag'
export { default as Dot } from './dot'
export { default as Keyboard } from './keyboard'
export { default as Checkbox } from './checkbox'
export { default as Fieldset } from './fieldset'
export { default as Modal } from './modal'
export { default as Spinner } from './spinner'
export { default as ButtonDropdown } from './button-dropdown'
export { default as Capacity } from './capacity'
export { default as Input } from './input'
export { default as Radio } from './radio'
export { default as Select } from './select'
export { default as Tabs } from './tabs'
export { default as Progress } from './progress'
export { default as Tree } from './tree'
export { default as Badge } from './badge'
export { default as AutoComplete } from './auto-complete'
export { default as Collapse } from './collapse'
export { default as Loading } from './loading'
export { default as Textarea } from './textarea'
export { default as Table } from './table'
export { default as Toggle } from './toggle'
export { default as Snippet } from './snippet'
export { default as Tooltip } from './tooltip'
export { default as Popover } from './popover'
export { default as Slider } from './slider'
export { default as Divider } from './divider'
export { default as User } from './user'
export { default as Page } from './page'
export { default as Grid } from './grid'
export { default as ButtonGroup } from './button-group'
export { default as Breadcrumbs } from './breadcrumbs'
export { default as Pagination } from './pagination'
export { default as CssBaseline } from './css-baseline'
export { default as useTheme } from './use-theme'

View File

@@ -9,4 +9,9 @@ export type InputComponentType = typeof Input & {
;(Input as InputComponentType).Textarea = Textarea
;(Input as InputComponentType).Password = InputPassword
export type { InputProps } from './input'
export type { InputTypes } from './input-props'
export type { InputPasswordProps } from './password'
export type { TextareaProps } from '../textarea/textarea'
export type { BindingsChangeTarget } from './use-input'
export default Input as InputComponentType

View File

@@ -1,11 +1,12 @@
import React from 'react'
import { NormalTypes } from '../utils/prop-types'
export type InputTypes = NormalTypes
export interface Props {
value?: string
initialValue?: string
placeholder?: string
type?: NormalTypes
type?: InputTypes
hymlType?: string
readOnly?: boolean
disabled?: boolean
@@ -29,7 +30,7 @@ export const defaultProps = {
readOnly: false,
clearable: false,
iconClickable: false,
type: 'default' as NormalTypes,
type: 'default' as InputTypes,
htmlType: 'text',
autoComplete: 'off',
className: '',

View File

@@ -1,3 +1,4 @@
import Keyboard from './keyboard'
export type { KeyboardProps } from './keyboard'
export default Keyboard

View File

@@ -1,3 +1,4 @@
import Link from './link'
export type { LinkProps } from './link'
export default Link

View File

@@ -1,3 +1,4 @@
import Loading from './loading'
export type { LoadingProps, LoadingTypes } from './loading'
export default Loading

View File

@@ -4,15 +4,16 @@ import { NormalTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from '../themes/presets'
import useScaleable, { withScaleable } from '../use-scaleable'
export type LoadingTypes = NormalTypes
interface Props {
type?: NormalTypes
type?: LoadingTypes
color?: string
className?: string
spaceRatio?: number
}
const defaultProps = {
type: 'default' as NormalTypes,
type: 'default' as LoadingTypes,
className: '',
spaceRatio: 1,
}
@@ -21,11 +22,11 @@ type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type LoadingProps = Props & NativeAttrs
const getIconBgColor = (
type: NormalTypes,
type: LoadingTypes,
palette: GeistUIThemesPalette,
color?: string,
) => {
const colors: { [key in NormalTypes]: string } = {
const colors: { [key in LoadingTypes]: string } = {
default: palette.accents_6,
secondary: palette.secondary,
success: palette.success,
@@ -46,11 +47,10 @@ const LoadingComponent: React.FC<React.PropsWithChildren<LoadingProps>> = ({
}: React.PropsWithChildren<LoadingProps> & typeof defaultProps) => {
const theme = useTheme()
const { SCALES } = useScaleable()
const bgColor = useMemo(() => getIconBgColor(type, theme.palette, color), [
type,
theme.palette,
color,
])
const bgColor = useMemo(
() => getIconBgColor(type, theme.palette, color),
[type, theme.palette, color],
)
return (
<div className={`loading-container ${className}`} {...props}>

View File

@@ -15,4 +15,9 @@ export type ModalComponentType = typeof Modal & {
;(Modal as ModalComponentType).Content = ModalContent
;(Modal as ModalComponentType).Action = ModalAction
export type { ModalProps } from './modal'
export type { ModalTitleProps } from './modal-title'
export type { ModalSubtitleProps } from './modal-subtitle'
export type { ModalActionProps } from './modal-action'
export type { ModalContentProps } from './modal-content'
export default Modal as ModalComponentType

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from 'react'
import React, { MouseEvent, useEffect, useMemo } from 'react'
import { createPortal } from 'react-dom'
import usePortal from '../utils/use-portal'
import ModalWrapper from './modal-wrapper'
@@ -15,6 +15,7 @@ interface Props {
disableBackdropClick?: boolean
onClose?: () => void
onOpen?: () => void
onContentClick?: (event: MouseEvent<HTMLElement>) => void
open?: boolean
wrapClassName?: string
}
@@ -28,12 +29,13 @@ type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type ModalProps = Props & NativeAttrs
const ModalComponent: React.FC<React.PropsWithChildren<ModalProps>> = ({
children,
disableBackdropClick,
onClose,
onOpen,
open,
onOpen,
onClose,
children,
wrapClassName,
onContentClick,
disableBackdropClick,
}: React.PropsWithChildren<ModalProps> & typeof defaultProps) => {
const portal = usePortal('modal')
const { SCALES } = useScaleable()
@@ -76,7 +78,11 @@ const ModalComponent: React.FC<React.PropsWithChildren<ModalProps>> = ({
if (!portal) return null
return createPortal(
<ModalContext.Provider value={modalConfig}>
<Backdrop onClick={closeFromBackdrop} visible={visible} width={SCALES.width(26)}>
<Backdrop
onClick={closeFromBackdrop}
onContentClick={onContentClick}
visible={visible}
width={SCALES.width(26)}>
<ModalWrapper visible={visible} className={wrapClassName}>
{withoutActionsChildren}
{hasActions && <ModalActions>{ActionsChildren}</ModalActions>}

View File

@@ -1,5 +1,4 @@
import Note from './note'
import { NoteProps } from './note'
export type Props = NoteProps
export type { NoteProps, NoteTypes } from './note'
export default Note

View File

@@ -4,15 +4,16 @@ import { NormalTypes } from '../utils/prop-types'
import { GeistUIThemes } from '../themes/presets'
import useScaleable, { withScaleable } from '../use-scaleable'
export type NoteTypes = NormalTypes
interface Props {
type?: NormalTypes
type?: NoteTypes
label?: string | boolean
filled?: boolean
className?: string
}
const defaultProps = {
type: 'default' as NormalTypes,
type: 'default' as NoteTypes,
label: 'note' as string | boolean,
filled: false,
className: '',
@@ -21,8 +22,8 @@ const defaultProps = {
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type NoteProps = Props & NativeAttrs
const getStatusColor = (type: NormalTypes, filled: boolean, theme: GeistUIThemes) => {
const colors: { [key in NormalTypes]?: string } = {
const getStatusColor = (type: NoteTypes, filled: boolean, theme: GeistUIThemes) => {
const colors: { [key in NoteTypes]?: string } = {
secondary: theme.palette.secondary,
success: theme.palette.success,
warning: theme.palette.warning,

View File

@@ -14,4 +14,8 @@ export type PageComponentType = typeof Page & {
;(Page as PageComponentType).Body = PageContent
;(Page as PageComponentType).Footer = PageFooter
export type { PageProps, PageRenderMode } from './page'
export type { PageHeaderProps } from './page-header'
export type { PageContentProps } from './page-content'
export type { PageFooterProps } from './page-footer'
export default Page as PageComponentType

View File

@@ -10,12 +10,12 @@ const defaultProps = {
}
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type PageHeaderProps = Props & NativeAttrs
export type PageFooterProps = Props & NativeAttrs
const PageFooterComponent: React.FC<React.PropsWithChildren<PageHeaderProps>> = ({
const PageFooterComponent: React.FC<React.PropsWithChildren<PageFooterProps>> = ({
children,
...props
}: React.PropsWithChildren<PageHeaderProps> & typeof defaultProps) => {
}: React.PropsWithChildren<PageFooterProps> & typeof defaultProps) => {
const { SCALES } = useScaleable()
return (

View File

@@ -34,15 +34,15 @@ const DotStyles: React.FC<unknown> = () => (
)
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type NoteProps = Props & NativeAttrs
export type PageProps = Props & NativeAttrs
const PageComponent: React.FC<React.PropsWithChildren<NoteProps>> = ({
const PageComponent: React.FC<React.PropsWithChildren<PageProps>> = ({
children,
render,
dotBackdrop,
className,
...props
}: React.PropsWithChildren<NoteProps> & typeof defaultProps) => {
}: React.PropsWithChildren<PageProps> & typeof defaultProps) => {
const theme = useTheme()
const { SCALES } = useScaleable()
const showDot = useMemo<boolean>(() => {

View File

@@ -9,4 +9,7 @@ export type PaginationComponentType = typeof Pagination & {
;(Pagination as PaginationComponentType).Previous = PaginationPrevious
;(Pagination as PaginationComponentType).Next = PaginationNext
export type { PaginationProps } from './pagination'
export type { PaginationPreviousProps } from './pagination-previous'
export type { PaginationNextProps } from './pagination-next'
export default Pagination as PaginationComponentType

View File

@@ -2,9 +2,9 @@ import React from 'react'
import PaginationItem from './pagination-item'
import { usePaginationContext } from './pagination-context'
export type PaginationNextProps = React.ButtonHTMLAttributes<any>
export type PaginationPreviousProps = React.ButtonHTMLAttributes<any>
const PaginationPrevious: React.FC<React.PropsWithChildren<PaginationNextProps>> = ({
const PaginationPrevious: React.FC<React.PropsWithChildren<PaginationPreviousProps>> = ({
children,
...props
}) => {

View File

@@ -2,11 +2,10 @@
exports[`Popover should render react-node 1`] = `
"<div class=\\"tooltip \\"><div></div><style>
:global(.tooltip-content.popover > .inner) {
padding: 8pt 0;
text-align: center;
}
</style><style>
:global(.tooltip-content.popover > .inner) {
padding: calc(0.9 * 16px) 0 calc(0.9 * 16px) 0;
}
</style><style>
.tooltip {
width: max-content;
display: inline-block;
@@ -16,11 +15,10 @@ exports[`Popover should render react-node 1`] = `
exports[`Popover should work with different placement 1`] = `
"<div class=\\"tooltip \\"><div></div><style>
:global(.tooltip-content.popover > .inner) {
padding: 8pt 0;
text-align: center;
}
</style><style>
:global(.tooltip-content.popover > .inner) {
padding: calc(0.9 * 16px) 0 calc(0.9 * 16px) 0;
}
</style><style>
.tooltip {
width: max-content;
display: inline-block;

View File

@@ -31,6 +31,30 @@ describe('Popover', () => {
expect(() => wrapper.unmount()).not.toThrow()
})
it('should work correctly with props', async () => {
const handler = jest.fn()
const Demos: React.FC<{ visible?: boolean }> = ({ visible }) => {
return (
<Popover visible={visible} onVisibleChange={handler} content="test">
<div />
</Popover>
)
}
const wrapper = mount(<Demos />)
expectPopoverIsHidden(wrapper)
wrapper.setProps({ visible: true })
await updateWrapper(wrapper, 350)
expectPopoverIsShow(wrapper)
expect(handler).toBeCalled()
wrapper.setProps({ visible: false })
await updateWrapper(wrapper, 350)
expectPopoverIsHidden(wrapper)
expect(() => wrapper.unmount()).not.toThrow()
})
it('should render react-node', async () => {
const wrapper = mount(
<Popover content={<p id="test">custom-content</p>}>
@@ -98,4 +122,75 @@ describe('Popover', () => {
expect(line.length).not.toBe(0)
expect(() => wrapper.unmount()).not.toThrow()
})
it('should close popover when item clicked', async () => {
const wrapper = mount(
<Popover content="test">
<Popover.Item id="item" />
</Popover>,
)
expectPopoverIsHidden(wrapper)
wrapper.find('.tooltip').simulate('click', nativeEvent)
await updateWrapper(wrapper, 350)
expectPopoverIsShow(wrapper)
const item = wrapper.find('#item').at(0)
item.simulate('click', nativeEvent)
await updateWrapper(wrapper, 350)
expectPopoverIsHidden(wrapper)
expect(() => wrapper.unmount()).not.toThrow()
})
it('should prevent close popover when item clicked', async () => {
const wrapper = mount(
<Popover content="test">
<Popover.Item id="item" disableAutoClose />
<Popover.Item id="item2" />
</Popover>,
)
expectPopoverIsHidden(wrapper)
wrapper.find('.tooltip').simulate('click', nativeEvent)
await updateWrapper(wrapper, 350)
expectPopoverIsShow(wrapper)
const item = wrapper.find('#item').at(0)
const item2 = wrapper.find('#item2').at(0)
item.simulate('click', nativeEvent)
await updateWrapper(wrapper, 350)
expectPopoverIsShow(wrapper)
item2.simulate('click', nativeEvent)
await updateWrapper(wrapper, 350)
expectPopoverIsHidden(wrapper)
expect(() => wrapper.unmount()).not.toThrow()
})
it('should prevent all items', async () => {
const wrapper = mount(
<Popover content="test" disableItemsAutoClose>
<Popover.Item id="item" />
<Popover.Item id="item2" />
</Popover>,
)
expectPopoverIsHidden(wrapper)
wrapper.find('.tooltip').simulate('click', nativeEvent)
await updateWrapper(wrapper, 350)
expectPopoverIsShow(wrapper)
const item = wrapper.find('#item').at(0)
const item2 = wrapper.find('#item2').at(0)
item.simulate('click', nativeEvent)
await updateWrapper(wrapper, 350)
expectPopoverIsShow(wrapper)
item2.simulate('click', nativeEvent)
await updateWrapper(wrapper, 350)
expectPopoverIsShow(wrapper)
expect(() => wrapper.unmount()).not.toThrow()
})
})

View File

@@ -1,7 +1,13 @@
import Popover from './popover'
import PopoverItem from './popover-item'
Popover.Item = PopoverItem
Popover.Option = PopoverItem
export type PopoverComponentType = typeof Popover & {
Item: typeof PopoverItem
Option: typeof PopoverItem
}
;(Popover as PopoverComponentType).Item = PopoverItem
;(Popover as PopoverComponentType).Option = PopoverItem
export default Popover
export type { PopoverProps, PopoverTriggerTypes, PopoverPlacement } from './popover'
export type { PopoverItemProps } from './popover-item'
export default Popover as PopoverComponentType

View File

@@ -0,0 +1,15 @@
import React, { useContext } from 'react'
export type PopoverConfig = {
disableItemsAutoClose: boolean
onItemClick: (e: React.MouseEvent<HTMLDivElement>) => void
}
const defaultContext = {
disableItemsAutoClose: false,
onItemClick: () => {},
}
export const PopoverContext = React.createContext<PopoverConfig>(defaultContext)
export const usePopoverContext = () => useContext<PopoverConfig>(PopoverContext)

View File

@@ -1,88 +1,98 @@
import React from 'react'
import useTheme from '../use-theme'
import useScaleable, { withScaleable } from '../use-scaleable'
import { usePopoverContext } from './popover-context'
interface Props {
line?: boolean
title?: boolean
disableAutoClose?: boolean
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void
}
const defaultProps = {
line: false,
title: false,
disableAutoClose: false,
className: '',
}
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type PopoverItemProps = Props & NativeAttrs
const PopoverItem: React.FC<React.PropsWithChildren<PopoverItemProps>> = ({
const PopoverItemComponent: React.FC<React.PropsWithChildren<PopoverItemProps>> = ({
children,
line,
title,
className,
onClick,
disableAutoClose,
...props
}: React.PropsWithChildren<PopoverItemProps> & typeof defaultProps) => {
const theme = useTheme()
const { SCALES } = useScaleable()
const { disableItemsAutoClose, onItemClick } = usePopoverContext()
const hasHandler = Boolean(onClick)
const dontCloseByClick = disableAutoClose || disableItemsAutoClose || title || line
const clickHandler = (event: React.MouseEvent<HTMLDivElement>) => {
onClick && onClick(event)
if (dontCloseByClick) {
return event.stopPropagation()
}
onItemClick(event)
}
return (
<>
<div
className={`item ${line ? 'line' : ''} ${title ? 'title' : ''} ${className}`}
onClick={clickHandler}
{...props}>
{children}
<style jsx>{`
.item {
display: flex;
box-sizing: border-box;
justify-content: flex-start;
align-items: center;
padding: 0.5rem ${theme.layout.gap};
color: ${theme.palette.accents_5};
font-size: 0.875rem;
line-height: 1.25rem;
text-align: left;
transition: color 0.1s ease 0s, background-color 0.1s ease 0s;
transition: color, background-color 150ms linear;
line-height: 1.25em;
font-size: ${SCALES.font(0.875)};
width: ${SCALES.width(1, 'auto')};
height: ${SCALES.height(1, 'auto')};
margin: ${SCALES.mt(0)} ${SCALES.mr(0)} ${SCALES.mb(0)} ${SCALES.ml(0)};
padding: ${SCALES.pt(0.5)} ${SCALES.pr(0.75)} ${SCALES.pb(0.5)}
${SCALES.pl(0.75)};
cursor: ${hasHandler ? 'pointer' : 'default'};
}
.item:hover {
color: ${theme.palette.foreground};
}
.item > :global(*) {
margin: 0;
}
.item > :global(.link) {
width: 100%;
padding: 0.5rem ${theme.layout.gap};
margin: -0.5rem -${theme.layout.gap};
}
.item.line {
line-height: 0;
height: 0;
padding: 0;
border-top: 1px solid ${theme.palette.border};
margin: 0.5rem 0;
width: 100%;
background-color: ${theme.palette.border};
height: ${SCALES.height(0.0625)};
margin: ${SCALES.mt(0.35)} ${SCALES.mr(0)} ${SCALES.mb(0.35)} ${SCALES.ml(0)};
width: ${SCALES.width(1, '100%')};
}
.item.title {
padding: 1.15rem;
font-weight: 500;
font-size: 0.83rem;
font-size: ${SCALES.font(0.925)};
color: ${theme.palette.foreground};
}
.item.title:first-of-type {
padding-top: 0.6rem;
padding-bottom: 0.6rem;
}
`}</style>
</div>
{title && <PopoverItem line title={false} className="" />}
{title && <PopoverItem line title={false} />}
</>
)
}
PopoverItem.defaultProps = defaultProps
PopoverItem.displayName = 'GeistPopoverItem'
PopoverItemComponent.defaultProps = defaultProps
PopoverItemComponent.displayName = 'GeistPopoverItem'
const PopoverItem = withScaleable(PopoverItemComponent)
export default PopoverItem

View File

@@ -1,20 +1,36 @@
import React, { useMemo } from 'react'
import useTheme from '../use-theme'
import Tooltip, { TooltipProps } from '../tooltip/tooltip'
import PopoverItem from '../popover/popover-item'
import React, { useEffect, useMemo, useState } from 'react'
import Tooltip, {
TooltipOnVisibleChange,
TooltipProps,
TooltipTypes,
} from '../tooltip/tooltip'
import { Placement, TriggerTypes } from '../utils/prop-types'
import { getReactNode } from '../utils/collections'
import useScaleable, { withScaleable } from '../use-scaleable'
import { PopoverContext, PopoverConfig } from './popover-context'
export type PopoverTriggerTypes = TriggerTypes
export type PopoverPlacement = Placement
interface Props {
content?: React.ReactNode | (() => React.ReactNode)
trigger?: TriggerTypes
trigger?: PopoverTriggerTypes
placement?: Placement
disableItemsAutoClose?: boolean
}
const defaultProps = {
trigger: 'click' as TriggerTypes,
disableItemsAutoClose: false,
trigger: 'click' as PopoverTriggerTypes,
placement: 'bottom' as Placement,
portalClassName: '',
initialVisible: false,
hideArrow: false,
type: 'default' as TooltipTypes,
enterDelay: 100,
leaveDelay: 150,
offset: 12,
className: '',
onVisibleChange: (() => {}) as TooltipOnVisibleChange,
}
type ExcludeTooltipProps = {
@@ -26,43 +42,62 @@ type ExcludeTooltipProps = {
export type PopoverProps = Props & Omit<TooltipProps, keyof ExcludeTooltipProps>
const Popover: React.FC<React.PropsWithChildren<PopoverProps>> = ({
const PopoverComponent: React.FC<React.PropsWithChildren<PopoverProps>> = ({
content,
children,
trigger,
placement,
initialVisible,
portalClassName,
disableItemsAutoClose,
onVisibleChange,
visible: customVisible,
...props
}) => {
const theme = useTheme()
}: React.PropsWithChildren<PopoverProps> & typeof defaultProps) => {
const { SCALES } = useScaleable()
const [visible, setVisible] = useState<boolean>(initialVisible)
const textNode = useMemo(() => getReactNode(content), [content])
const onChildClick = () => {
onPopoverVisibleChange(false)
}
const value = useMemo<PopoverConfig>(
() => ({
onItemClick: onChildClick,
disableItemsAutoClose,
}),
[disableItemsAutoClose],
)
const onPopoverVisibleChange = (next: boolean) => {
setVisible(next)
onVisibleChange(next)
}
useEffect(() => {
if (customVisible === undefined) return
onPopoverVisibleChange(customVisible)
}, [customVisible])
return (
<Tooltip
text={textNode}
trigger={trigger}
placement={placement}
portalClassName={`popover ${portalClassName}`}
{...props}>
{children}
<style jsx>{`
:global(.tooltip-content.popover > .inner) {
padding: ${theme.layout.gapHalf} 0;
text-align: center;
}
`}</style>
</Tooltip>
<PopoverContext.Provider value={value}>
<Tooltip
text={textNode}
trigger={trigger}
placement={placement}
portalClassName={`popover ${portalClassName}`}
visible={visible}
onVisibleChange={onPopoverVisibleChange}
{...props}>
{children}
<style jsx>{`
:global(.tooltip-content.popover > .inner) {
padding: ${SCALES.pt(0.9)} ${SCALES.pr(0)} ${SCALES.pb(0.9)} ${SCALES.pl(0)};
}
`}</style>
</Tooltip>
</PopoverContext.Provider>
)
}
type PopoverComponent<P = {}> = React.FC<P> & {
Item: typeof PopoverItem
Option: typeof PopoverItem
}
type ComponentProps = Partial<typeof defaultProps> &
Omit<Props, keyof typeof defaultProps> &
Partial<Omit<TooltipProps, keyof ExcludeTooltipProps>>
Popover.defaultProps = defaultProps
export default Popover as PopoverComponent<ComponentProps>
PopoverComponent.defaultProps = defaultProps
PopoverComponent.displayName = 'GeistPopover'
const Popover = withScaleable(PopoverComponent)
export default Popover

View File

@@ -1,3 +1,4 @@
import Progress from './progress'
export type { ProgressProps, ProgressColors, ProgressTypes } from './progress'
export default Progress

View File

@@ -8,6 +8,7 @@ import useScaleable, { withScaleable } from '../use-scaleable'
export type ProgressColors = {
[key: number]: string
}
export type ProgressTypes = NormalTypes
interface Props {
value?: number
@@ -15,14 +16,14 @@ interface Props {
fixedTop?: boolean
fixedBottom?: boolean
colors?: ProgressColors
type?: NormalTypes
type?: ProgressTypes
className?: string
}
const defaultProps = {
value: 0,
max: 100,
type: 'default' as NormalTypes,
type: 'default' as ProgressTypes,
fixedTop: false,
fixedBottom: false,
className: '',
@@ -34,10 +35,10 @@ export type ProgressProps = Props & NativeAttrs
const getCurrentColor = (
ratio: number,
palette: GeistUIThemesPalette,
type: NormalTypes,
type: ProgressTypes,
colors: ProgressColors = {},
): string => {
const defaultColors: { [key in NormalTypes]: string } = {
const defaultColors: { [key in ProgressTypes]: string } = {
default: palette.foreground,
success: palette.success,
secondary: palette.secondary,

View File

@@ -11,4 +11,7 @@ export type RadioComponentType = typeof Radio & {
;(Radio as RadioComponentType).Description = RadioDescription
;(Radio as RadioComponentType).Desc = RadioDescription
export type { RadioProps, RadioEvent, RadioEventTarget, RadioTypes } from './radio'
export type { RadioGroupProps } from './radio-group'
export type { RadioDescriptionProps } from './radio-description'
export default Radio as RadioComponentType

View File

@@ -8,10 +8,10 @@ import { NormalTypes } from '../utils/prop-types'
import { getColors } from './styles'
import useScaleable, { withScaleable } from '../use-scaleable'
interface RadioEventTarget {
export type RadioTypes = NormalTypes
export interface RadioEventTarget {
checked: boolean
}
export interface RadioEvent {
target: RadioEventTarget
stopPropagation: () => void
@@ -22,14 +22,14 @@ export interface RadioEvent {
interface Props {
checked?: boolean
value?: string | number
type?: NormalTypes
type?: RadioTypes
className?: string
disabled?: boolean
onChange?: (e: RadioEvent) => void
}
const defaultProps = {
type: 'default' as NormalTypes,
type: 'default' as RadioTypes,
disabled: false,
className: '',
}
@@ -65,10 +65,10 @@ const RadioComponent: React.FC<React.PropsWithChildren<RadioProps>> = ({
}, [groupValue, radioValue])
}
const { label, border, bg } = useMemo(() => getColors(theme.palette, type), [
theme.palette,
type,
])
const { label, border, bg } = useMemo(
() => getColors(theme.palette, type),
[theme.palette, type],
)
const isDisabled = useMemo(() => disabled || disabledAll, [disabled, disabledAll])
const changeHandler = (event: React.ChangeEvent) => {

View File

@@ -1,5 +1,4 @@
import Row from './row'
import { RowProps } from './row'
export type Props = RowProps
export type { RowProps } from './row'
export default Row

View File

@@ -6,4 +6,6 @@ export type SelectComponentType = typeof Select & {
}
;(Select as SelectComponentType).Option = SelectOption
export type { SelectProps, SelectTypes } from './select'
export type { SelectOptionProps } from './select-option'
export default Select as SelectComponentType

View File

@@ -13,9 +13,10 @@ import { getColors } from './styles'
import Ellipsis from '../shared/ellipsis'
import useScaleable, { withScaleable } from '../use-scaleable'
export type SelectTypes = NormalTypes
interface Props {
disabled?: boolean
type?: NormalTypes
type?: SelectTypes
value?: string | string[]
initialValue?: string | string[]
placeholder?: React.ReactNode | string
@@ -33,7 +34,7 @@ interface Props {
const defaultProps = {
disabled: false,
type: 'default' as NormalTypes,
type: 'default' as SelectTypes,
icon: SelectIcon as React.ComponentType,
pure: false,
multiple: false,

View File

@@ -48,15 +48,15 @@ describe('Backdrop', () => {
expect(() => wrapper.unmount()).not.toThrow()
})
it('should be prevent event from the container', () => {
it('should be pass event from the container', () => {
const handler = jest.fn()
const wrapper = mount(
<Backdrop onClick={handler} visible>
<Backdrop visible onContentClick={handler}>
<span>test-value</span>
</Backdrop>,
)
wrapper.find('.content').simulate('click', nativeEvent)
expect(handler).not.toHaveBeenCalled()
expect(handler).toHaveBeenCalled()
handler.mockRestore()
})

View File

@@ -1,4 +1,4 @@
import React, { MouseEvent, useCallback } from 'react'
import React, { MouseEvent } from 'react'
import useTheme from '../use-theme'
import CssTransition from './css-transition'
import useCurrentState from '../utils/use-current-state'
@@ -7,11 +7,13 @@ interface Props {
onClick?: (event: MouseEvent<HTMLElement>) => void
visible?: boolean
width?: string
onContentClick?: (event: MouseEvent<HTMLElement>) => void
}
const defaultProps = {
onClick: () => {},
visible: false,
onContentClick: () => {},
}
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
@@ -23,6 +25,7 @@ const Backdrop: React.FC<React.PropsWithChildren<BackdropProps>> = React.memo(
onClick,
visible,
width,
onContentClick,
...props
}: React.PropsWithChildren<BackdropProps> & typeof defaultProps) => {
const theme = useTheme()
@@ -31,9 +34,6 @@ const Backdrop: React.FC<React.PropsWithChildren<BackdropProps>> = React.memo(
if (IsContentMouseDownRef.current) return
onClick && onClick(event)
}
const childrenClickHandler = useCallback((event: MouseEvent<HTMLElement>) => {
event.stopPropagation()
}, [])
const mouseUpHandler = () => {
if (!IsContentMouseDownRef.current) return
const timer = setTimeout(() => {
@@ -51,7 +51,7 @@ const Backdrop: React.FC<React.PropsWithChildren<BackdropProps>> = React.memo(
{...props}>
<div className="layer" />
<div
onClick={childrenClickHandler}
onClick={onContentClick}
className="content"
onMouseDown={() => setIsContentMouseDown(true)}>
{children}

View File

@@ -1,3 +1,4 @@
import Slider from './slider'
export type { SliderProps, SliderTypes } from './slider'
export default Slider

View File

@@ -15,10 +15,11 @@ import { getColors } from './styles'
import { NormalTypes } from '../utils/prop-types'
import useScaleable, { withScaleable } from '../use-scaleable'
export type SliderTypes = NormalTypes
interface Props {
hideValue?: boolean
value?: number
type?: NormalTypes
type?: SliderTypes
initialValue?: number
step?: number
max?: number
@@ -31,7 +32,7 @@ interface Props {
const defaultProps = {
hideValue: false,
type: 'default' as NormalTypes,
type: 'default' as SliderTypes,
initialValue: 0,
step: 1,
min: 0,
@@ -92,11 +93,10 @@ const SliderComponent: React.FC<React.PropsWithChildren<SliderProps>> = ({
const sliderRef = useRef<HTMLDivElement>(null)
const dotRef = useRef<HTMLDivElement>(null)
const currentRatio = useMemo(() => ((value - min) / (max - min)) * 100, [
value,
max,
min,
])
const currentRatio = useMemo(
() => ((value - min) / (max - min)) * 100,
[value, max, min],
)
const setLastOffsetManually = (val: number) => {
const width = getRefWidth(sliderRef)

View File

@@ -1,3 +1,5 @@
import Snippet from './snippet'
export type { SnippetProps, ToastTypes } from './snippet'
export type { CopyTypes, SnippetTypes } from '../utils/prop-types'
export default Snippet

View File

@@ -7,11 +7,12 @@ import useClipboard from '../utils/use-clipboard'
import useToasts from '../use-toasts'
import useScaleable, { withScaleable } from '../use-scaleable'
export type ToastTypes = NormalTypes
interface Props {
text?: string | string[]
symbol?: string
toastText?: string
toastType?: NormalTypes
toastType?: ToastTypes
filled?: boolean
copy?: CopyTypes
type?: SnippetTypes
@@ -22,7 +23,7 @@ const defaultProps = {
filled: false,
symbol: '$',
toastText: 'Copied to clipboard!',
toastType: 'success' as NormalTypes,
toastType: 'success' as ToastTypes,
copy: 'default' as CopyTypes,
type: 'default' as SnippetTypes,
className: '',
@@ -57,11 +58,10 @@ const SnippetComponent: React.FC<React.PropsWithChildren<SnippetProps>> = ({
const ref = useRef<HTMLPreElement>(null)
const isMultiLine = text && Array.isArray(text)
const style = useMemo(() => getStyles(type, theme.palette, filled), [
type,
theme.palette,
filled,
])
const style = useMemo(
() => getStyles(type, theme.palette, filled),
[type, theme.palette, filled],
)
const showCopyIcon = useMemo(() => copyType !== 'prevent', [copyType])
const childText = useMemo<string | undefined | null>(() => {
if (isMultiLine) return textArrayToString(text as string[])

View File

@@ -1,5 +1,4 @@
import Spacer from './spacer'
import { SpacerProps } from './spacer'
export type Props = SpacerProps
export type { SpacerProps } from './spacer'
export default Spacer

View File

@@ -1,3 +1,4 @@
import Spinner from './spinner'
export type { SpinnerProps } from './spinner'
export default Spinner

View File

@@ -6,4 +6,19 @@ export type TableComponentType = typeof Table & {
}
;(Table as TableComponentType).Column = TableColumn
export type {
TableProps,
TableOnRow,
TableOnChange,
TableOnCell,
TableDataSource,
} from './table'
export type { TableColumnProps } from './table-column'
export type {
TableOperation,
TableCellActions,
TableCellActionRemove,
TableCellActionUpdate,
TableCellData,
} from './table-cell'
export default Table as TableComponentType

View File

@@ -8,4 +8,5 @@ export type TabsComponentType = typeof Tabs & {
;(Tabs as TabsComponentType).Item = TabsItem
;(Tabs as TabsComponentType).Tab = TabsItem
export type { TabsProps } from './tabs'
export default Tabs as TabsComponentType

View File

@@ -1,5 +1,4 @@
import Tag from './tag'
import { TagProps } from './tag'
export type Props = TagProps
export type { TagProps, TagColors, TagTypes } from './tag'
export default Tag

View File

@@ -4,14 +4,15 @@ import { SnippetTypes } from '../utils/prop-types'
import { GeistUIThemesPalette } from '../themes/presets'
import useScaleable, { withScaleable } from '../use-scaleable'
export type TagTypes = SnippetTypes
interface Props {
type?: SnippetTypes
type?: TagTypes
invert?: boolean
className?: string
}
const defaultProps = {
type: 'default' as SnippetTypes,
type: 'default' as TagTypes,
invert: false,
className: '',
}
@@ -25,13 +26,9 @@ export type TagColors = {
borderColor: string
}
const getColors = (
type: SnippetTypes,
palette: GeistUIThemesPalette,
invert: boolean,
) => {
const getColors = (type: TagTypes, palette: GeistUIThemesPalette, invert: boolean) => {
const colors: {
[key in SnippetTypes]: Pick<TagColors, 'color'> & Partial<TagColors>
[key in TagTypes]: Pick<TagColors, 'color'> & Partial<TagColors>
} = {
default: {
color: palette.foreground,

View File

@@ -1,3 +1,4 @@
import Text from './text'
export type { TextProps, TextTypes } from './text'
export default Text

View File

@@ -3,6 +3,7 @@ import { NormalTypes } from '../utils/prop-types'
import TextChild from './child'
import { withScaleable } from '../use-scaleable'
export type TextTypes = NormalTypes
interface Props {
h1?: boolean
h2?: boolean
@@ -19,7 +20,7 @@ interface Props {
em?: boolean
blockquote?: boolean
className?: string
type?: NormalTypes
type?: TextTypes
}
const defaultProps = {
@@ -38,7 +39,7 @@ const defaultProps = {
em: false,
blockquote: false,
className: '',
type: 'default' as NormalTypes,
type: 'default' as TextTypes,
}
type ElementMap = { [key in keyof JSX.IntrinsicElements]?: boolean }

View File

@@ -37,7 +37,6 @@ exports[`Textarea should render correctly 1`] = `
font-size: var(--textarea-font-size);
width: 100%;
height: var(--textarea-height);
resize: none;
border: none;
outline: none;
padding: calc(0.5 * 16px) calc(0.5 * 16px) calc(0.5 * 16px)
@@ -95,7 +94,6 @@ exports[`Textarea should work with different styles 1`] = `
font-size: var(--textarea-font-size);
width: 100%;
height: var(--textarea-height);
resize: none;
border: none;
outline: none;
padding: calc(0.5 * 16px) calc(0.5 * 16px) calc(0.5 * 16px)
@@ -149,7 +147,6 @@ exports[`Textarea should work with different styles 1`] = `
font-size: var(--textarea-font-size);
width: 100%;
height: var(--textarea-height);
resize: none;
border: none;
outline: none;
padding: calc(0.5 * 16px) calc(0.5 * 16px) calc(0.5 * 16px)
@@ -167,7 +164,7 @@ exports[`Textarea should work with different styles 1`] = `
textarea:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0 30px #fff inset !important;
}
</style></div><div class=\\"wrapper \\"><textarea minheight=\\"100px\\"></textarea><style>
</style></div><div class=\\"wrapper \\"><textarea height=\\"100px\\"></textarea><style>
.wrapper {
display: inline-flex;
box-sizing: border-box;
@@ -179,7 +176,7 @@ exports[`Textarea should work with different styles 1`] = `
min-width: 12.5rem;
max-width: 95vw;
--textarea-font-size: calc(0.875 * 16px);
--textarea-height: auto;
--textarea-height: 100px;
width: initial;
height: var(--textarea-height);
margin: 0 0 0 0;
@@ -203,7 +200,6 @@ exports[`Textarea should work with different styles 1`] = `
font-size: var(--textarea-font-size);
width: 100%;
height: var(--textarea-height);
resize: none;
border: none;
outline: none;
padding: calc(0.5 * 16px) calc(0.5 * 16px) calc(0.5 * 16px)

View File

@@ -15,7 +15,7 @@ describe('Textarea', () => {
<div>
<Textarea type="secondary" />
<Textarea width="20%" />
<Textarea minHeight="100px" />
<Textarea height="100px" />
</div>,
)
expect(wrapper.html()).toMatchSnapshot()

View File

@@ -1,3 +1,4 @@
import Textarea from './textarea'
export type { TextareaProps, TextareaResizes, TextareaTypes } from './textarea'
export default Textarea

View File

@@ -5,29 +5,29 @@ import { getColors } from '../input/styles'
import useScaleable, { withScaleable } from '../use-scaleable'
const resizeTypes = tuple('none', 'both', 'horizontal', 'vertical', 'initial', 'inherit')
type ResizeTypes = typeof resizeTypes[number]
export type TextareaResizes = typeof resizeTypes[number]
export type TextareaTypes = NormalTypes
interface Props {
value?: string
initialValue?: string
placeholder?: string
type?: NormalTypes
type?: TextareaTypes
disabled?: boolean
readOnly?: boolean
onChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
onFocus?: (e: React.FocusEvent<HTMLTextAreaElement>) => void
onBlur?: (e: React.FocusEvent<HTMLTextAreaElement>) => void
className?: string
resize?: ResizeTypes
resize?: TextareaResizes
}
const defaultProps = {
initialValue: '',
type: 'default' as NormalTypes,
type: 'default' as TextareaTypes,
disabled: false,
readOnly: false,
className: '',
resize: 'none' as ResizeTypes,
resize: 'none' as TextareaResizes,
}
type NativeAttrs = Omit<React.TextareaHTMLAttributes<any>, keyof Props>
@@ -145,7 +145,6 @@ const TextareaComponent = React.forwardRef<
font-size: var(--textarea-font-size);
width: 100%;
height: var(--textarea-height);
resize: none;
border: none;
outline: none;
padding: ${SCALES.pt(0.5)} ${SCALES.pr(0.5)} ${SCALES.pb(0.5)}

View File

@@ -1,3 +1,13 @@
import Themes from './themes'
export type { GeistUserTheme } from './themes'
export type {
GeistUIThemes,
GeistUIThemesBreakpoints,
BreakpointsItem,
GeistUIThemesExpressiveness,
GeistUIThemesFont,
GeistUIThemesLayout,
GeistUIThemesPalette,
} from './presets'
export default Themes

View File

@@ -1,3 +1,10 @@
import Toggle from './toggle'
export type {
ToggleProps,
ToggleSize,
ToggleEvent,
ToggleEventTarget,
ToggleTypes,
} from './toggle'
export default Toggle

View File

@@ -4,10 +4,10 @@ import { NormalTypes } from '../utils/prop-types'
import { getColors } from './styles'
import useScaleable, { withScaleable } from '../use-scaleable'
interface ToggleEventTarget {
export type ToggleTypes = NormalTypes
export interface ToggleEventTarget {
checked: boolean
}
export interface ToggleEvent {
target: ToggleEventTarget
stopPropagation: () => void
@@ -20,12 +20,12 @@ interface Props {
initialChecked?: boolean
onChange?: (ev: ToggleEvent) => void
disabled?: boolean
type?: NormalTypes
type?: ToggleTypes
className?: string
}
const defaultProps = {
type: 'default' as NormalTypes,
type: 'default' as ToggleTypes,
disabled: false,
initialChecked: false,
className: '',

View File

@@ -29,7 +29,7 @@ describe('Tooltip', () => {
await updateWrapper(wrapper, 150)
wrapper.find('.tooltip').simulate('mouseLeave', nativeEvent)
await updateWrapper(wrapper, 150)
await updateWrapper(wrapper, 350)
expectTooltipIsHidden(wrapper)
})
@@ -40,11 +40,11 @@ describe('Tooltip', () => {
</div>,
)
wrapper.find('.tooltip').simulate('mouseEnter', nativeEvent)
await updateWrapper(wrapper, 150)
await updateWrapper(wrapper, 350)
expectTooltipIsShow(wrapper)
wrapper.find('.tooltip').simulate('mouseLeave', nativeEvent)
await updateWrapper(wrapper, 150)
await updateWrapper(wrapper, 350)
expectTooltipIsHidden(wrapper)
})

View File

@@ -0,0 +1,33 @@
import { MutableRefObject } from 'react'
export interface ReactiveDomReact {
top: number
bottom: number
left: number
right: number
width: number
height: number
}
const defaultRect: ReactiveDomReact = {
top: -1000,
left: -1000,
right: -1000,
bottom: -1000,
width: 0,
height: 0,
}
export const getRect = (ref: MutableRefObject<HTMLElement | null>): ReactiveDomReact => {
if (!ref || !ref.current) return defaultRect
const rect = ref.current.getBoundingClientRect()
return {
...rect,
width: rect.width || rect.right - rect.left,
height: rect.height || rect.bottom - rect.top,
top: rect.top + document.documentElement.scrollTop,
bottom: rect.bottom + document.documentElement.scrollTop,
left: rect.left + document.documentElement.scrollLeft,
right: rect.right + document.documentElement.scrollLeft,
}
}

View File

@@ -1,3 +1,10 @@
import Tooltip from './tooltip'
export type {
TooltipProps,
TooltipOnVisibleChange,
TooltipTypes,
TooltipTriggers,
TooltipPlacement,
} from './tooltip'
export default Tooltip

View File

@@ -101,47 +101,49 @@ export const getPosition = (
export const getIconPosition = (
placement: Placement,
offset: number,
offsetX: string,
offsetY: string,
offsetAbsolute: string = '3px',
): TooltipIconPosition => {
const positions: { [key in Placement]?: TooltipIconPosition } = {
top: {
top: 'auto',
right: 'auto',
left: '50%',
bottom: `${offset}px`,
bottom: `${offsetAbsolute}`,
transform: 'translate(-50%, 100%) rotate(-90deg)',
},
topStart: {
top: 'auto',
right: 'auto',
left: '5%',
bottom: `${offset}px`,
left: `${offsetX}`,
bottom: `${offsetAbsolute}`,
transform: 'translate(0, 100%) rotate(-90deg)',
},
topEnd: {
top: 'auto',
right: '5%',
right: `${offsetX}`,
left: 'auto',
bottom: `${offset}px`,
bottom: `${offsetAbsolute}`,
transform: 'translate(0, 100%) rotate(-90deg)',
},
bottom: {
top: `${offset}px`,
top: `${offsetAbsolute}`,
right: 'auto',
left: '50%',
bottom: 'auto',
transform: 'translate(-50%, -100%) rotate(90deg)',
},
bottomStart: {
top: `${offset}px`,
top: `${offsetAbsolute}`,
right: 'auto',
left: '5%',
left: `${offsetX}`,
bottom: 'auto',
transform: 'translate(0, -100%) rotate(90deg)',
},
bottomEnd: {
top: `${offset}px`,
right: '5%',
top: `${offsetAbsolute}`,
right: `${offsetX}`,
left: 'auto',
bottom: 'auto',
transform: 'translate(0, -100%) rotate(90deg)',
@@ -154,18 +156,18 @@ export const getIconPosition = (
transform: 'translate(100%, -50%) rotate(180deg)',
},
leftStart: {
top: '10%',
top: `${offsetY}`,
right: '0',
left: 'auto',
bottom: 'auto',
transform: 'translate(100%, 0) rotate(180deg)',
transform: 'translate(100%, -50%) rotate(180deg)',
},
leftEnd: {
top: 'auto',
right: '0',
left: 'auto',
bottom: '10%',
transform: 'translate(100%, 0) rotate(180deg)',
bottom: `${offsetY}`,
transform: 'translate(100%, 50%) rotate(180deg)',
},
right: {
top: '50%',
@@ -175,18 +177,18 @@ export const getIconPosition = (
transform: 'translate(-100%, -50%) rotate(0deg)',
},
rightStart: {
top: '10%',
top: `${offsetY}`,
right: 'auto',
left: '0',
bottom: 'auto',
transform: 'translate(-100%, 0) rotate(0deg)',
transform: 'translate(-100%, -50%) rotate(0deg)',
},
rightEnd: {
top: 'auto',
right: 'auto',
left: '0',
bottom: '10%',
transform: 'translate(-100%, 0) rotate(0deg)',
bottom: `${offsetY}`,
transform: 'translate(-100%, 50%) rotate(0deg)',
},
}

View File

@@ -9,6 +9,8 @@ import { getColors } from './styles'
import { getPosition, TooltipPosition, defaultTooltipPosition } from './placement'
import TooltipIcon from './tooltip-icon'
import { Placement, SnippetTypes } from '../utils/prop-types'
import useScaleable from '../use-scaleable'
import { getRect } from './helper'
interface Props {
parent?: MutableRefObject<HTMLElement | null> | undefined
@@ -18,38 +20,11 @@ interface Props {
hideArrow: boolean
offset: number
className?: string
iconOffset: TooltipIconOffset
}
interface ReactiveDomReact {
top: number
bottom: number
left: number
right: number
width: number
height: number
}
const defaultRect: ReactiveDomReact = {
top: -1000,
left: -1000,
right: -1000,
bottom: -1000,
width: 0,
height: 0,
}
const getRect = (ref: MutableRefObject<HTMLElement | null>): ReactiveDomReact => {
if (!ref || !ref.current) return defaultRect
const rect = ref.current.getBoundingClientRect()
return {
...rect,
width: rect.width || rect.right - rect.left,
height: rect.height || rect.bottom - rect.top,
top: rect.top + document.documentElement.scrollTop,
bottom: rect.bottom + document.documentElement.scrollTop,
left: rect.left + document.documentElement.scrollLeft,
right: rect.right + document.documentElement.scrollLeft,
}
export type TooltipIconOffset = {
x: string
y: string
}
const TooltipContent: React.FC<React.PropsWithChildren<Props>> = ({
@@ -57,12 +32,14 @@ const TooltipContent: React.FC<React.PropsWithChildren<Props>> = ({
parent,
visible,
offset,
iconOffset,
placement,
type,
className,
hideArrow,
}) => {
const theme = useTheme()
const { SCALES } = useScaleable()
const el = usePortal('tooltip')
const selfRef = useRef<HTMLDivElement>(null)
const [rect, setRect] = useState<TooltipPosition>(defaultTooltipPosition)
@@ -95,33 +72,36 @@ const TooltipContent: React.FC<React.PropsWithChildren<Props>> = ({
ref={selfRef}
onClick={preventHandler}>
<div className="inner">
{!hideArrow && (
<TooltipIcon
placement={placement}
bgColor={colors.bgColor}
shadow={hasShadow}
/>
)}
{!hideArrow && <TooltipIcon placement={placement} shadow={hasShadow} />}
{children}
</div>
<style jsx>{`
.tooltip-content {
--tooltip-icon-offset-x: ${iconOffset.x};
--tooltip-icon-offset-y: ${iconOffset.y};
--tooltip-content-bg: ${colors.bgColor};
box-sizing: border-box;
position: absolute;
width: auto;
top: ${rect.top};
left: ${rect.left};
transform: ${rect.transform};
background-color: ${colors.bgColor};
background-color: var(--tooltip-content-bg);
color: ${colors.color};
border-radius: ${theme.layout.radius};
padding: 0;
z-index: 1000;
box-shadow: ${hasShadow ? theme.expressiveness.shadowMedium : 'none'};
width: ${SCALES.width(1, 'auto')};
height: ${SCALES.height(1, 'auto')};
}
.inner {
padding: ${theme.layout.gapHalf} ${theme.layout.gap};
box-sizing: border-box;
position: relative;
font-size: ${SCALES.font(1)};
padding: ${SCALES.pt(0.65)} ${SCALES.pr(0.9)} ${SCALES.pb(0.65)}
${SCALES.pl(0.9)};
height: 100%;
}
`}</style>
</div>

View File

@@ -5,20 +5,24 @@ import useTheme from '../use-theme'
interface Props {
placement: Placement
bgColor: string
shadow: boolean
}
const TooltipIcon: React.FC<Props> = ({ placement, bgColor, shadow }) => {
const TooltipIcon: React.FC<Props> = ({ placement, shadow }) => {
const theme = useTheme()
const { transform, top, left, right, bottom } = useMemo(
() => getIconPosition(placement, 3),
() =>
getIconPosition(
placement,
'var(--tooltip-icon-offset-x)',
'var(--tooltip-icon-offset-y)',
),
[placement],
)
const bgColorWithDark = useMemo(() => {
if (!shadow || theme.type !== 'dark') return bgColor
if (!shadow || theme.type !== 'dark') return 'var(--tooltip-content-bg)'
return theme.palette.accents_2
}, [theme.type, bgColor, shadow])
}, [theme.type, shadow])
return (
<span>
@@ -41,4 +45,4 @@ const TooltipIcon: React.FC<Props> = ({ placement, bgColor, shadow }) => {
)
}
export default React.memo(TooltipIcon)
export default TooltipIcon

View File

@@ -1,18 +1,22 @@
import React, { useEffect, useRef, useState } from 'react'
import TooltipContent from './tooltip-content'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import TooltipContent, { TooltipIconOffset } from './tooltip-content'
import useClickAway from '../utils/use-click-away'
import { TriggerTypes, Placement, SnippetTypes } from '../utils/prop-types'
import { withScaleable } from '../use-scaleable'
import { getRect } from './helper'
export type TooltipOnVisibleChange = (visible: boolean) => void
export type TooltipTypes = SnippetTypes
export type TooltipTriggers = TriggerTypes
export type TooltipPlacement = Placement
interface Props {
text: string | React.ReactNode
type?: SnippetTypes
placement?: Placement
type?: TooltipTypes
placement?: TooltipPlacement
visible?: boolean
initialVisible?: boolean
hideArrow?: boolean
trigger?: TriggerTypes
trigger?: TooltipTriggers
enterDelay?: number
leaveDelay?: number
offset?: number
@@ -24,11 +28,11 @@ interface Props {
const defaultProps = {
initialVisible: false,
hideArrow: false,
type: 'default' as SnippetTypes,
trigger: 'hover' as TriggerTypes,
placement: 'top' as Placement,
type: 'default' as TooltipTypes,
trigger: 'hover' as TooltipTriggers,
placement: 'top' as TooltipPlacement,
enterDelay: 100,
leaveDelay: 0,
leaveDelay: 150,
offset: 12,
className: '',
portalClassName: '',
@@ -38,7 +42,7 @@ const defaultProps = {
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type TooltipProps = Props & NativeAttrs
const Tooltip: React.FC<React.PropsWithChildren<TooltipProps>> = ({
const TooltipComponent: React.FC<React.PropsWithChildren<TooltipProps>> = ({
children,
initialVisible,
text,
@@ -58,12 +62,21 @@ const Tooltip: React.FC<React.PropsWithChildren<TooltipProps>> = ({
const timer = useRef<number>()
const ref = useRef<HTMLDivElement>(null)
const [visible, setVisible] = useState<boolean>(initialVisible)
const iconOffset = useMemo<TooltipIconOffset>(() => {
if (!ref?.current) return { x: '0.75em', y: '0.75em' }
const rect = getRect(ref)
return {
x: `${rect.width ? rect.width / 2 : 0}px`,
y: `${rect.height ? rect.height / 2 : 0}px`,
}
}, [ref?.current])
const contentProps = {
type,
visible,
offset,
placement,
hideArrow,
iconOffset,
parent: ref,
className: portalClassName,
}
@@ -83,14 +96,14 @@ const Tooltip: React.FC<React.PropsWithChildren<TooltipProps>> = ({
timer.current = window.setTimeout(() => handler(true), enterDelay)
return
}
timer.current = window.setTimeout(() => handler(false), leaveDelay)
const leaveDelayWithoutClick = trigger === 'click' ? 0 : leaveDelay
timer.current = window.setTimeout(() => handler(false), leaveDelayWithoutClick)
}
const mouseEventHandler = (next: boolean) => trigger === 'hover' && changeVisible(next)
const clickEventHandler = () => trigger === 'click' && changeVisible(!visible)
useClickAway(ref, () => trigger === 'click' && changeVisible(false))
useEffect(() => {
if (customVisible === undefined) return
changeVisible(customVisible)
@@ -116,6 +129,7 @@ const Tooltip: React.FC<React.PropsWithChildren<TooltipProps>> = ({
)
}
Tooltip.defaultProps = defaultProps
Tooltip.displayName = 'GiestTooltip'
TooltipComponent.defaultProps = defaultProps
TooltipComponent.displayName = 'GiestTooltip'
const Tooltip = withScaleable(TooltipComponent)
export default Tooltip

View File

@@ -2,9 +2,9 @@ import React from 'react'
import { mount } from 'enzyme'
import { Tree } from 'components'
import { nativeEvent } from 'tests/utils'
import { FileTreeValue } from '../tree'
import { TreeFile } from '../tree'
const mockFiles: Array<FileTreeValue> = [
const mockFiles: Array<TreeFile> = [
{
type: 'file',
name: 'cs.js',

View File

@@ -9,4 +9,5 @@ export type TreeComponentType = typeof Tree & {
;(Tree as TreeComponentType).File = TreeFile
;(Tree as TreeComponentType).Folder = TreeFolder
export type { TreeProps, TreeFile } from './tree'
export default Tree as TreeComponentType

View File

@@ -3,21 +3,21 @@ import TreeFile from './tree-file'
import TreeFolder from './tree-folder'
import { TreeContext } from './tree-context'
import { tuple } from '../utils/prop-types'
import { sortChildren } from './/tree-help'
import { sortChildren } from './tree-help'
const FileTreeValueType = tuple('directory', 'file')
const directoryType = FileTreeValueType[0]
export type FileTreeValue = {
export type TreeFile = {
type: typeof FileTreeValueType[number]
name: string
extra?: string
files?: Array<FileTreeValue>
files?: Array<TreeFile>
}
interface Props {
value?: Array<FileTreeValue>
value?: Array<TreeFile>
initialExpand?: boolean
onClick?: (path: string) => void
className?: string
@@ -31,7 +31,7 @@ const defaultProps = {
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type TreeProps = Props & NativeAttrs
const makeChildren = (value: Array<FileTreeValue> = []) => {
const makeChildren = (value: Array<TreeFile> = []) => {
if (!value || !value.length) return null
return value
.sort((a, b) => {

View File

@@ -1,3 +1,4 @@
import { useAllThemes } from './all-themes-context'
export type { AllThemesConfig } from './all-themes-context'
export default useAllThemes

View File

@@ -1,3 +1,4 @@
import useBodyScroll from './use-body-scroll'
export type { BodyScrollOptions } from './use-body-scroll'
export default useBodyScroll

View File

@@ -1,19 +1,26 @@
import { MutableRefObject, useEffect } from 'react'
import { MutableRefObject, useEffect, useRef } from 'react'
export type ClickAwayGetContainer = () => HTMLElement | null
const useClickAway = (
ref: MutableRefObject<HTMLElement | null>,
handler: (event: Event) => void,
) => {
const handlerRef = useRef(handler)
useEffect(() => {
handlerRef.current = handler
}, [handlerRef])
useEffect(() => {
const callback = (event: Event) => {
const el = ref.current
if (!event || !el || el.contains((event as any).target)) return
handler(event)
handlerRef.current(event)
}
document.addEventListener('click', callback, { capture: true })
return () => document.removeEventListener('click', callback, { capture: true })
}, [ref, handler])
document.addEventListener('click', callback)
return () => document.removeEventListener('click', callback)
}, [ref])
}
export default useClickAway

View File

@@ -1,3 +1,4 @@
import useClipboard from './use-clipboard'
export type { UseClipboardOptions, UseClipboardResult } from './use-clipboard'
export default useClipboard

View File

@@ -1,5 +1,11 @@
import useKeyboard from './use-keyboard'
import { KeyMod, KeyCode } from './codes'
export default useKeyboard
export { KeyMod, KeyCode }
export type {
UseKeyboardHandler,
KeyboardOptions,
KeyboardResult,
UseKeyboard,
} from './use-keyboard'
export default useKeyboard

Some files were not shown because too many files have changed in this diff Show More