Skip to content

v4 #64

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 14 commits into from
Jul 28, 2020
Merged

v4 #64

Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
jobs:
test:
docker:
- image: circleci/node:8.11
- image: circleci/node:10.18
steps:
- checkout

Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
10.18
15 changes: 5 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,6 @@ E.g:
<Box is={Link} to="/#">Login</Box>
```

##### innerRef

Type: `function`

Callback that gets passed a ref to inner DOM node (or component if the `is` prop is set to a React component type).

##### clearfix

Type: `boolean`
Expand Down Expand Up @@ -310,14 +304,15 @@ setClassNamePrefix('📦')

### Safe `href`s

By default `ui-box` does not ensure that urls use safe protocols when passed to an element. But we built this functionality into `ui-box` to protect the end users of the products you are building. You can alter this by using `configureSafeHref({enabled?: boolean, origin?: string})`. This will ensure that only safe protocols are used (`http:`, `https:`, `mailto:`, `tel:`, and `data:`) and that the correct `rel` values are added (`noopener`, `noreferrer`(for external links)).
By default `ui-box` ensures that urls use safe protocols when passed to an element. We built this functionality into `ui-box` to protect the end users of the products you are building. You can opt-out of this by using `configureSafeHref({enabled?: boolean, origin?: string})`. This allows you to configure which protocols are acceptable (`http:`, `https:`, `mailto:`, `tel:`, and `data:`) and that the correct `rel` values are added (`noopener`, `noreferrer`(for external links)).

```js
import { configureSafeHref } from 'ui-box'
configureSafeHref({
enabled: true,
enabled: true, // the default behavior
})
```

```js
import { configureSafeHref } from 'ui-box'
configureSafeHref({
Expand All @@ -326,10 +321,10 @@ configureSafeHref({
})
```

Additionally you can overwrite the behavoir on an individual component basis using the prop `allowUnsafeHref`
Additionally you can override the behavior on an individual component basis using the prop `allowUnsafeHref`

```jsx
<Box is="a" href="javascript:alert('hi')" allowUnsafeHref={true}>This is unsafe</Box>
<Box is="a" href="javascript:alert('hi')" allowUnsafeHref>This is unsafe</Box>
```

### Server side rendering
Expand Down
21 changes: 12 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ui-box",
"version": "3.3.0",
"version": "4.0.0-3",
"description": "Blazing Fast React UI Primitive",
"contributors": [
"Jeroen Ransijn (https://twitter.com/jeroen_ransijn)",
Expand All @@ -14,20 +14,23 @@
],
"repository": "segmentio/ui-box",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
"files": [
"dist"
"dist/src"
],
"sideEffects": false,
"engines": {
"node": ">=10.18"
},
"scripts": {
"test": "xo && nyc ava",
"prepublishOnly": "rm -rf dist && yarn run build",
"dev": "start-storybook -p 9009",
"build": "tsc",
"build-storybook": "build-storybook -s .storybook/static -o .out",
"release": "np",
"benchmark": "echo ui-box && react-benchmark tools/benchmarks/box.js",
"benchmark": "echo ui-box && react-benchmark dist/tools/benchmarks/box.js",
"size": "size-limit",
"coverage": "nyc report --reporter=html"
},
Expand All @@ -41,6 +44,7 @@
},
"devDependencies": {
"@babel/core": "^7.4.4",
"@size-limit/preset-big-lib": "^4.5.4",
"@storybook/react": "^5.0.1",
"@storybook/storybook-deployer": "^2.8.1",
"@types/enzyme": "^3.9.1",
Expand Down Expand Up @@ -72,7 +76,7 @@
"react-dom": "^16.8.4",
"react-test-renderer": "^16.8.4",
"sinon": "^7.2.7",
"size-limit": "^1.3.1",
"size-limit": "^4.5.4",
"ts-node": "^8.1.0",
"typescript": "^3.4.5",
"webpack": "^4.30.0",
Expand Down Expand Up @@ -149,9 +153,8 @@
},
"size-limit": [
{
"webpack": false,
"path": "dist/index.js",
"limit": "5 KB",
"path": "dist/src/index.js",
"limit": "50 KB",
"running": false,
"gzip": false
}
Expand Down
29 changes: 14 additions & 15 deletions src/box.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React from 'react'
import React, { forwardRef } from 'react'
import PropTypes from 'prop-types'
import {BoxComponent} from './types/box-types'
import {propTypes} from './enhancers'
import { BoxProps } from './types/box-types'
import { propTypes } from './enhancers'
import enhanceProps from './enhance-props'
import {extractAnchorProps, getUseSafeHref} from './utils/safeHref'
import { extractAnchorProps, getUseSafeHref } from './utils/safeHref'

const Box: BoxComponent = ({ is = 'div', innerRef, children, allowUnsafeHref, ...props }) => {
const Box = forwardRef(<E extends React.ElementType>({ is, children, allowUnsafeHref, ...props }: BoxProps<E>, ref: React.Ref<Element>) => {
// Convert the CSS props to class names (and inject the styles)
const {className, enhancedProps: parsedProps} = enhanceProps(props)

parsedProps.className = className

if (innerRef) {
parsedProps.ref = innerRef
if (ref) {
parsedProps.ref = ref
}

/**
Expand All @@ -27,22 +27,21 @@ const Box: BoxComponent = ({ is = 'div', innerRef, children, allowUnsafeHref, ..
parsedProps.rel = safeRel
}

return React.createElement(is, parsedProps, children)
}
return React.createElement(is || 'div', parsedProps, children)
}) as <E extends React.ElementType = 'div'>(props: BoxProps<E>) => JSX.Element

// @ts-ignore
Box.displayName = 'Box'

// @ts-ignore
Box.propTypes = {
...propTypes,
innerRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({ current: PropTypes.element })
]),
is: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.elementType])
is: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.elementType]),
allowUnsafeHref: PropTypes.bool
}

// @ts-ignore
Box.defaultProps = {
innerRef: null,
is: 'div',
boxSizing: 'border-box'
}
Expand Down
7 changes: 4 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as cache from './cache'
import * as styles from './styles'

export {default} from './box'
export {default as splitProps} from './utils/split-props'
export {default as splitBoxProps} from './utils/split-box-props'
export { default } from './box'
export { default as splitProps } from './utils/split-props'
export { default as splitBoxProps } from './utils/split-box-props'
export { setClassNamePrefix } from './get-class-name'
export { configureSafeHref } from './utils/safeHref'
export { BoxProps, BoxOwnProps, EnhancerProps, PropsOf, PolymorphicBoxProps, BoxComponent } from './types/box-types'

export {
background,
Expand Down
77 changes: 29 additions & 48 deletions src/types/box-types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react'
import { EnhancerProps } from './enhancers'
import { DomNodes } from './dom-nodes'

export { EnhancerProps }

Expand All @@ -10,60 +9,42 @@ export { EnhancerProps }
*/
export type Without<T, K> = Pick<T, Exclude<keyof T, K>>

/**
* "is" prop
* @template P Props
*/
export type Is<P = any> = React.ElementType<P>
export type PropsOf<
E extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>
> = JSX.LibraryManagedAttributes<E, React.ComponentPropsWithRef<E>>

/**
* Custom Ref to handle `is` prop
* Generic component props with "is" prop
* @template P Additional props
* @template T React component or string element
*/
export type RefType<T> = T extends keyof DomNodes
? DomNodes[T] // Get the DOM node type
: T extends typeof React.Component
? T['prototype'] // Convert component class type back to a class instance
: never // Functional components can't have refs
export type BoxOwnProps<E extends React.ElementType = React.ElementType, P = {}> = Without<EnhancerProps, keyof P> & {
/**
* Replaces the underlying element
*/
is?: E

/**
* Allows the high level value of safeHref to be overwritten on an individual component basis
*/
allowUnsafeHref?: boolean
}

/**
* Remove box props from object `T` if they're present
* @template T Object
*/
type WithoutBoxProps<T> = Without<T, "is" | "innerRef">
export type BoxProps<E extends React.ElementType> = BoxOwnProps<E> & Without<PropsOf<E>, keyof BoxOwnProps>

/**
* Grab components passed to the `is` prop and return their props
* @template T Component type
* Convenience type for defining your own component props that extend Box and pass-through props
*/
type InheritedProps<T extends Is> = WithoutBoxProps<React.ComponentPropsWithoutRef<T>>
export type PolymorphicBoxProps<
E extends React.ElementType,
// optional additional props (which we get removed from BoxOwnProps and PropsOf)
// this is useful for defining some pass-through props on a wrapper for Box
P = {}
> = BoxOwnProps<E, P> & Without<PropsOf<E>, keyof (BoxOwnProps & P)> & P

/**
* Generic component props with "is" prop
* @template P Additional props
* @template T React component or string element
* Convenience type for defining your own components that extend Box and pass-through props
*/
export type BoxProps<T extends Is> = InheritedProps<T> &
EnhancerProps & {
/**
* Replaces the underlying element
*/
is?: T

/**
* Callback that gets passed a ref to inner DOM node (or component if the
* `is` prop is set to a React component type).
*/
innerRef?: React.Ref<RefType<T>>

/**
* Allows the high level value of safeHref to be overwritten on an individual component basis
*/
allowUnsafeHref?: boolean
}

export interface BoxComponent {
<T extends Is>(props: BoxProps<T>): React.ReactElement | null
propTypes?: React.FunctionComponent['propTypes']
defaultProps?: React.FunctionComponent['defaultProps']
displayName?: React.FunctionComponent['displayName']
}
export type BoxComponent<P = {}, D extends React.ElementType = React.ElementType> = <
E extends React.ElementType = D
>(props: PolymorphicBoxProps<E, P>) => JSX.Element
Loading