Skip to content

Commit

Permalink
v4 (#64)
Browse files Browse the repository at this point in the history
* replace innerRef with forwardRef (#61)

* enable safe hrefs by default (#63)

* use separate sourcemaps and fix yarn size-limit

* format file, remove comments from dist

* lint --fix a few things

* node 10.18+

* upgrade ts deps and use simpler polymorphic typing (#71)

* v4.0.0-1

* update types again

* export types

* v4.0.0-2

* revert propsOf back

* v4.0.0-3
  • Loading branch information
Matt Shwery authored Jul 28, 2020
1 parent 9cdecc4 commit a00ce6a
Show file tree
Hide file tree
Showing 16 changed files with 1,462 additions and 1,428 deletions.
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">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

0 comments on commit a00ce6a

Please sign in to comment.