Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions components/box/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react'
import {mount} from 'enzyme'
import {Box} from 'components'

describe('Button', () => {
it('should render correctly', () => {
const wrapper = mount(<Box>Box</Box>)
expect(() => wrapper.unmount()).not.toThrow()
})

it('should render as the provided element', async () => {
const wrapper = mount(<Box as='a' href='https://geist.com'>Box</Box>)

expect(wrapper.exists('a[href="https://geist.com"]')).toBe(true)
expect(() => wrapper.unmount()).not.toThrow()
})

it('should render with provided styles', async () => {
document.body.innerHTML = '<div id="root"></div>';
const wrapper = mount(<Box px='2rem' w='300px' h='100px' my='2rem'>Box</Box>, {
attachTo: document.querySelector('#root') as HTMLDivElement
})

expect(wrapper.find('div').getDOMNode()).toHaveStyle({
display: 'block',
lineHeight: '100px',
fontSize: 'calc(1 * 16px)',
width: '300px',
height: '100px',
margin: '2rem 0px 2rem 0px',
visibility: 'visible',
padding: '0px 2rem 0px 2rem'
})
expect(() => wrapper.unmount()).not.toThrow()
})

it('filter out scale related props', () => {
const wrapper = mount(<Box px='2rem'>Box</Box>)

expect(wrapper.exists('div')).toBe(true)
expect(wrapper.getDOMNode().hasAttribute('px')).toBe(false)
expect(() => wrapper.unmount()).not.toThrow()
})

it('should forward the provided ref', () => {
const ref = React.createRef<HTMLDivElement>()
const wrapper = mount(<Box ref={ref}>Box</Box>)
expect(wrapper.find('div').getDOMNode()).toEqual(ref.current)
expect(() => wrapper.unmount()).not.toThrow()
})
})
102 changes: 102 additions & 0 deletions components/box/box.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from 'react'
import {DynamicScales, makeScaleHandler, ScaleProps} from "../use-scale";
import useClasses from "../use-classes";
import useTheme from "../use-theme";

type PropsOf<E extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>> =
JSX.LibraryManagedAttributes<E, React.ComponentPropsWithRef<E>>;

export interface BoxOwnProps<E extends React.ElementType = React.ElementType> {
as?: E;
}

export type BoxProps<E extends React.ElementType> = BoxOwnProps<E> &
Omit<PropsOf<E>, keyof BoxOwnProps> & ScaleProps;

const defaultElement = 'div';

export type BoxComponent = <E extends React.ElementType = typeof defaultElement>(
props: BoxProps<E>,
) => React.ReactElement | null

export const Box: BoxComponent = React.forwardRef(
<E extends React.ElementType = typeof defaultElement>
({as, children, className, ...restProps}: BoxProps<E>, ref: typeof restProps.ref | null) => {
const Element = as || defaultElement;
const {layout} = useTheme()
const {
paddingLeft,
pl,
paddingRight,
pr,
paddingTop,
pt,
paddingBottom,
pb,
marginTop,
mt,
marginRight,
mr,
marginBottom,
mb,
marginLeft,
ml,
px,
py,
mx,
my,
width,
height,
font,
w,
h,
margin,
padding,
unit = layout.unit,
scale = 1,
...innerProps
} = restProps

const SCALES: DynamicScales = {
pt: makeScaleHandler(paddingTop ?? pt ?? py ?? padding, scale, unit),
pr: makeScaleHandler(paddingRight ?? pr ?? px ?? padding, scale, unit),
pb: makeScaleHandler(paddingBottom ?? pb ?? py ?? padding, scale, unit),
pl: makeScaleHandler(paddingLeft ?? pl ?? px ?? padding, scale, unit),
px: makeScaleHandler(px ?? paddingLeft ?? paddingRight ?? pl ?? pr ?? padding, scale, unit),
py: makeScaleHandler(py ?? paddingTop ?? paddingBottom ?? pt ?? pb ?? padding, scale, unit),
mt: makeScaleHandler(marginTop ?? mt ?? my ?? margin, scale, unit),
mr: makeScaleHandler(marginRight ?? mr ?? mx ?? margin, scale, unit),
mb: makeScaleHandler(marginBottom ?? mb ?? my ?? margin, scale, unit),
ml: makeScaleHandler(marginLeft ?? ml ?? mx ?? margin, scale, unit),
mx: makeScaleHandler(mx ?? marginLeft ?? marginRight ?? ml ?? mr ?? margin, scale, unit),
my: makeScaleHandler(my ?? marginTop ?? marginBottom ?? mt ?? mb ?? margin, scale, unit),
width: makeScaleHandler(width ?? w, scale, unit),
height: makeScaleHandler(height ?? h, scale, unit),
font: makeScaleHandler(font, scale, unit),
}

return (
<Element
className={useClasses('box', className)}
ref={ref}
{...innerProps}
>
{children}
<style jsx>{`
.box {
line-height: ${SCALES.height(1)};
font-size: ${SCALES.font(1)};
width: ${SCALES.width(0, 'auto')};
height: ${SCALES.height(0, 'auto')};
padding: ${SCALES.pt(0)} ${SCALES.pr(0)} ${SCALES.pb(0)} ${SCALES.pl(0)};
margin: ${SCALES.mt(0)} ${SCALES.mr(0)} ${SCALES.mb(0)} ${SCALES.ml(0)};
}
`}</style>
</Element>
);
},
);

// Box.displayName = 'GeistBox'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot about this, I'll work on it


export default Box
4 changes: 4 additions & 0 deletions components/box/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Box from './box'

export type { BoxProps } from './box'
export default Box
3 changes: 3 additions & 0 deletions components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export type { AvatarProps, AvatarGroupProps } from './avatar'
export { default as Badge } from './badge'
export type { BadgeProps, BadgeAnchorProps } from './badge'

export {default as Box} from './box'
export type {BoxProps} from './box'

export { default as Breadcrumbs } from './breadcrumbs'
export type {
BreadcrumbsProps,
Expand Down
28 changes: 27 additions & 1 deletion components/use-scale/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import {
GetAllScalePropsFunction,
GetScalePropsFunction,
ScaleProps,
ScalePropKeys,
ScalePropKeys, DynamicLayoutPipe,
} from './scale-context'
import {isCSSNumberValue} from "../utils/collections";

export const generateGetScaleProps = <P>(
props: P & ScaleProps,
Expand Down Expand Up @@ -37,3 +38,28 @@ export const generateGetAllScaleProps = <P>(
}
return getAllScaleProps
}

export const reduceScaleCoefficient = (scale: number) => {
if (scale === 1) return scale
const diff = Math.abs((scale - 1) / 2)
return scale > 1 ? 1 + diff : 1 - diff
}

export const makeScaleHandler =
(attrValue: string | number | undefined, scale: number, unit: string): DynamicLayoutPipe =>
(scale1x, defaultValue) => {
// 0 means disable scale and the default value is 0
if (scale1x === 0) {
scale1x = 1
defaultValue = defaultValue || 0
}
const factor = reduceScaleCoefficient(scale) * scale1x
if (typeof attrValue === 'undefined') {
if (typeof defaultValue !== 'undefined') return `${defaultValue}`
return `calc(${factor} * ${unit})`
}

if (!isCSSNumberValue(attrValue)) return `${attrValue}`
const customFactor = factor * Number(attrValue)
return `calc(${customFactor} * ${unit})`
}
59 changes: 17 additions & 42 deletions components/use-scale/with-scale.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import React, { forwardRef } from 'react'
import { DynamicLayoutPipe, ScaleConfig, ScaleContext, ScaleProps } from './scale-context'
import { ScaleConfig, ScaleContext, ScaleProps } from './scale-context'
import useTheme from '../use-theme'
import { isCSSNumberValue } from '../utils/collections'
import { generateGetAllScaleProps, generateGetScaleProps } from './utils'

const reduceScaleCoefficient = (scale: number) => {
if (scale === 1) return scale
const diff = Math.abs((scale - 1) / 2)
return scale > 1 ? 1 + diff : 1 - diff
}
import {generateGetAllScaleProps, generateGetScaleProps, makeScaleHandler} from './utils'

const withScale = <T, P = {}>(
Render: React.ComponentType<P & { ref?: React.Ref<T> }>,
Expand Down Expand Up @@ -47,43 +40,25 @@ const withScale = <T, P = {}>(
scale = 1,
...innerProps
} = props
const makeScaleHandler =
(attrValue: string | number | undefined): DynamicLayoutPipe =>
(scale1x, defaultValue) => {
// 0 means disable scale and the default value is 0
if (scale1x === 0) {
scale1x = 1
defaultValue = defaultValue || 0
}
const factor = reduceScaleCoefficient(scale) * scale1x
if (typeof attrValue === 'undefined') {
if (typeof defaultValue !== 'undefined') return `${defaultValue}`
return `calc(${factor} * ${unit})`
}

if (!isCSSNumberValue(attrValue)) return `${attrValue}`
const customFactor = factor * Number(attrValue)
return `calc(${customFactor} * ${unit})`
}

const value: ScaleConfig = {
unit: unit,
SCALES: {
pt: makeScaleHandler(paddingTop ?? pt ?? py ?? padding),
pr: makeScaleHandler(paddingRight ?? pr ?? px ?? padding),
pb: makeScaleHandler(paddingBottom ?? pb ?? py ?? padding),
pl: makeScaleHandler(paddingLeft ?? pl ?? px ?? padding),
px: makeScaleHandler(px ?? paddingLeft ?? paddingRight ?? pl ?? pr ?? padding),
py: makeScaleHandler(py ?? paddingTop ?? paddingBottom ?? pt ?? pb ?? padding),
mt: makeScaleHandler(marginTop ?? mt ?? my ?? margin),
mr: makeScaleHandler(marginRight ?? mr ?? mx ?? margin),
mb: makeScaleHandler(marginBottom ?? mb ?? my ?? margin),
ml: makeScaleHandler(marginLeft ?? ml ?? mx ?? margin),
mx: makeScaleHandler(mx ?? marginLeft ?? marginRight ?? ml ?? mr ?? margin),
my: makeScaleHandler(my ?? marginTop ?? marginBottom ?? mt ?? mb ?? margin),
width: makeScaleHandler(width ?? w),
height: makeScaleHandler(height ?? h),
font: makeScaleHandler(font),
pt: makeScaleHandler(paddingTop ?? pt ?? py ?? padding, scale, unit),
pr: makeScaleHandler(paddingRight ?? pr ?? px ?? padding, scale, unit),
pb: makeScaleHandler(paddingBottom ?? pb ?? py ?? padding, scale, unit),
pl: makeScaleHandler(paddingLeft ?? pl ?? px ?? padding, scale, unit),
px: makeScaleHandler(px ?? paddingLeft ?? paddingRight ?? pl ?? pr ?? padding, scale, unit),
py: makeScaleHandler(py ?? paddingTop ?? paddingBottom ?? pt ?? pb ?? padding, scale, unit),
mt: makeScaleHandler(marginTop ?? mt ?? my ?? margin, scale, unit),
mr: makeScaleHandler(marginRight ?? mr ?? mx ?? margin, scale, unit),
mb: makeScaleHandler(marginBottom ?? mb ?? my ?? margin, scale, unit),
ml: makeScaleHandler(marginLeft ?? ml ?? mx ?? margin, scale, unit),
mx: makeScaleHandler(mx ?? marginLeft ?? marginRight ?? ml ?? mr ?? margin, scale, unit),
my: makeScaleHandler(my ?? marginTop ?? marginBottom ?? mt ?? mb ?? margin, scale, unit),
width: makeScaleHandler(width ?? w, scale, unit),
height: makeScaleHandler(height ?? h, scale, unit),
font: makeScaleHandler(font, scale, unit),
},
getScaleProps: generateGetScaleProps(props),
getAllScaleProps: generateGetAllScaleProps(props),
Expand Down
55 changes: 55 additions & 0 deletions pages/en-us/components/box.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Box } from 'components'
import { Layout, Playground, Attributes } from 'lib/components'

export const meta = {
title: 'Box',
group: 'Layout',
}

## Box

Polymorphic scalable component.

<Playground
scope={{ Box }}
code={`
<Box>
Hello
</Box>
`}
/>

<Playground
title="Polymorphic property"
desc="Provide an element to render the Box as."
scope={{ Box }}
code={`
<Box as='a' href='#'>
I am an anchor tag
</Box>
`}
/>

<Playground
title="Scalability"
desc="Use props to scale the Box."
scope={{ Box }}
code={`
<Box font={3} py='3rem' mx='auto' width='300px'>
I am scalable
</Box>
`}
/>

<Attributes edit="/pages/en-us/components/page.mdx">
<Attributes.Title>Box.Props</Attributes.Title>

| Attribute | Description | Type | Accepted values | Default |
| --------------- | ---------------------------------- | --------------------------------- | --------------------------- | --------- |
| **as** | render mode | `React.ElementType` | `'span', 'p', 'form', ...` | `div` |
| ... | scale props | `ScaleProps` | `'width', 'px', 'font', ...`| - |
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |

</Attributes>

export default ({ children }) => <Layout meta={meta}>{children}</Layout>