From e46e2c379dc73056ec3770eb84a027518e45f2e6 Mon Sep 17 00:00:00 2001 From: olfedias Date: Thu, 8 Nov 2018 16:26:18 +0200 Subject: [PATCH 01/11] chore(package): correct peer dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6bb1aa099a..9c7111a4cc 100644 --- a/package.json +++ b/package.json @@ -171,8 +171,8 @@ "webpack-hot-middleware": "^2.18.2" }, "peerDependencies": { - "react": ">=0.14.0 <= 16", - "react-dom": ">=0.14.0 <= 16" + "react": "^16.3.0", + "react-dom": "^16.3.0" }, "resolutions": { "create-react-context": "0.2.2" From 386e455dd7d5db6ff95ab66e2762c2a3d3f5962b Mon Sep 17 00:00:00 2001 From: olfedias Date: Thu, 8 Nov 2018 16:42:37 +0200 Subject: [PATCH 02/11] docs(CHANGELOG): add entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c962eaa31e..71cca04716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixes - Fix endMedia to not be removed from DOM on mouseleave for `ListItem` @musingh1 ([#278](https://github.com/stardust-ui/react/pull/278)) +- Fix React's version in `peerDependencies` @layershifter ([#452](https://github.com/stardust-ui/react/pull/452)) ### Features - Make `Grid` keyboard navigable by implementing `gridBehavior` @sophieH29 ([#398](https://github.com/stardust-ui/react/pull/398)) From 720cd64319ab1d764b431e50c8fa5c1a64b9b8bc Mon Sep 17 00:00:00 2001 From: olfedias Date: Fri, 9 Nov 2018 15:33:17 +0200 Subject: [PATCH 03/11] docs(ComponentExample): fix types --- .github/add-a-feature.md | 1 - .../ComponentExample/ComponentExample.tsx | 8 ++++---- .../ComponentExample/ComponentExampleTitle.tsx | 11 +++++------ docs/src/components/DocsBehaviorRoot.tsx | 1 - 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/add-a-feature.md b/.github/add-a-feature.md index e7eec8c397..146e599bbb 100644 --- a/.github/add-a-feature.md +++ b/.github/add-a-feature.md @@ -8,7 +8,6 @@ Add a feature - [Propose feature](#propose-feature) - [Prototype](#prototype) - [Spec out the API](#spec-out-the-api) -- [Component anatomy](#component-anatomy) - [Create a component](#create-a-component) - [How to create a component](#how-to-create-a-component) - [Good practice](#good-practice) diff --git a/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx b/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx index febab227f4..dbf0a29314 100644 --- a/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx +++ b/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx @@ -21,8 +21,8 @@ import { mergeThemeVariables } from '../../../../../src/lib/mergeThemes' import { ThemeContext } from '../../../context/theme-context' export interface ComponentExampleProps extends RouteComponentProps { - title: string - description: string + title: React.ReactNode + description?: React.ReactNode examplePath: string themeName?: string } @@ -724,10 +724,10 @@ class ComponentExample extends React.Component ( +const ComponentExampleWithTheme = props => ( {({ themeName }) => } -)) +) export default withRouter(ComponentExampleWithTheme) diff --git a/docs/src/components/ComponentDoc/ComponentExample/ComponentExampleTitle.tsx b/docs/src/components/ComponentDoc/ComponentExample/ComponentExampleTitle.tsx index a19f061bbc..edaa36674b 100644 --- a/docs/src/components/ComponentDoc/ComponentExample/ComponentExampleTitle.tsx +++ b/docs/src/components/ComponentDoc/ComponentExample/ComponentExampleTitle.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types' import * as React from 'react' import { Header } from 'semantic-ui-react' @@ -6,12 +5,12 @@ const titleStyle = { margin: 0, } -export default class ComponentExampleTitle extends React.PureComponent { - static propTypes = { - description: PropTypes.node, - title: PropTypes.node, - } +interface ComponentExampleTitleProps { + description?: React.ReactNode + title: React.ReactNode +} +export default class ComponentExampleTitle extends React.PureComponent { render() { const { description, title } = this.props return ( diff --git a/docs/src/components/DocsBehaviorRoot.tsx b/docs/src/components/DocsBehaviorRoot.tsx index 7247e2e05e..5b25b68eab 100644 --- a/docs/src/components/DocsBehaviorRoot.tsx +++ b/docs/src/components/DocsBehaviorRoot.tsx @@ -54,7 +54,6 @@ class DocsBehaviorRoot extends React.Component {
From 700f2a43077813e50cf879faddcf2433af945f86 Mon Sep 17 00:00:00 2001 From: olfedias Date: Fri, 9 Nov 2018 15:33:56 +0200 Subject: [PATCH 04/11] feat(handleRef): add a new util --- src/lib/handleRef.ts | 25 +++++++++++++++++++++++++ src/lib/index.ts | 2 ++ 2 files changed, 27 insertions(+) create mode 100644 src/lib/handleRef.ts diff --git a/src/lib/handleRef.ts b/src/lib/handleRef.ts new file mode 100644 index 0000000000..09b188dbdb --- /dev/null +++ b/src/lib/handleRef.ts @@ -0,0 +1,25 @@ +import * as React from 'react' + +type HandleRefProps = { [K in TProp]?: React.Ref } + +const handleRef =

(props: HandleRefProps, propName: P, node: N) => { + const ref: React.Ref = props[propName] + + if (process.env.NODE_ENV !== 'production') { + if (typeof ref === 'string') { + throw new Error("We don't support refs as string") + } + } + + if (typeof ref === 'function') { + ref(node) + return + } + + if (typeof ref === 'object') { + // @ts-ignore + ref.current = node + } +} + +export default handleRef diff --git a/src/lib/index.ts b/src/lib/index.ts index a5366da356..4e1b9fddde 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -15,6 +15,8 @@ export { default as getElementType } from './getElementType' export { default as getUnhandledProps } from './getUnhandledProps' export { default as mergeThemes } from './mergeThemes' export { default as renderComponent, RenderResultConfig } from './renderComponent' + +export { default as handleRef } from './handleRef' export { htmlImageProps, htmlInputAttrs, From 25a62c6cb4108c6c3aff1c26867889bd26602af5 Mon Sep 17 00:00:00 2001 From: olfedias Date: Fri, 9 Nov 2018 15:34:46 +0200 Subject: [PATCH 05/11] feat(Ref): add willUnmount(), export component on top level --- .../components/Ref/Types/RefExampleRef.tsx | 63 +++++++++++++++++++ .../examples/components/Ref/Types/index.tsx | 21 +++++++ docs/src/examples/components/Ref/index.tsx | 10 +++ src/components/Ref/Ref.tsx | 23 ++++--- src/index.ts | 1 + 5 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 docs/src/examples/components/Ref/Types/RefExampleRef.tsx create mode 100644 docs/src/examples/components/Ref/Types/index.tsx create mode 100644 docs/src/examples/components/Ref/index.tsx diff --git a/docs/src/examples/components/Ref/Types/RefExampleRef.tsx b/docs/src/examples/components/Ref/Types/RefExampleRef.tsx new file mode 100644 index 0000000000..af52ca3b33 --- /dev/null +++ b/docs/src/examples/components/Ref/Types/RefExampleRef.tsx @@ -0,0 +1,63 @@ +import React from 'react' +import { Button, Grid, Ref, Segment } from '@stardust-ui/react' + +class RefExampleRef extends React.Component { + state = { isMounted: false } + + createdRef = React.createRef() + functionalRef = null + + handleRef = node => (this.functionalRef = node) + + componentDidMount() { + this.setState({ isMounted: true }) + } + + render() { + const { isMounted } = this.state + + return ( + + + + + + + + + + + {isMounted && ( + +

+              {JSON.stringify(
+                {
+                  nodeName: this.functionalRef.nodeName,
+                  nodeType: this.functionalRef.nodeType,
+                  textContent: this.functionalRef.textContent,
+                },
+                null,
+                2,
+              )}
+            
+
+              {JSON.stringify(
+                {
+                  nodeName: this.createdRef.current.nodeName,
+                  nodeType: this.createdRef.current.nodeType,
+                  textContent: this.createdRef.current.textContent,
+                },
+                null,
+                2,
+              )}
+            
+
+ )} + + ) + } +} + +export default RefExampleRef diff --git a/docs/src/examples/components/Ref/Types/index.tsx b/docs/src/examples/components/Ref/Types/index.tsx new file mode 100644 index 0000000000..698ebddebd --- /dev/null +++ b/docs/src/examples/components/Ref/Types/index.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' + +import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample' +import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection' + +const RefTypesExamples = () => ( + + + A component exposes the innerRef prop that always returns the DOM node of + both functional and class component children. + + } + examplePath="components/Ref/Types/RefExampleRef" + /> + +) + +export default RefTypesExamples diff --git a/docs/src/examples/components/Ref/index.tsx b/docs/src/examples/components/Ref/index.tsx new file mode 100644 index 0000000000..6225f5d6f4 --- /dev/null +++ b/docs/src/examples/components/Ref/index.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' +import Types from './Types' + +const RefExamples: React.SFC = () => ( +
+ +
+) + +export default RefExamples diff --git a/src/components/Ref/Ref.tsx b/src/components/Ref/Ref.tsx index 384b449d8f..2aecc565f6 100644 --- a/src/components/Ref/Ref.tsx +++ b/src/components/Ref/Ref.tsx @@ -1,19 +1,20 @@ import * as PropTypes from 'prop-types' -import * as _ from 'lodash' -import { Children, Component } from 'react' +import * as React from 'react' import { findDOMNode } from 'react-dom' -import { ReactChildren } from 'utils' + +import { ReactChildren } from '../../../types/utils' +import { handleRef } from '../../lib' export interface RefProps { children?: ReactChildren - innerRef?: (ref: HTMLElement) => void + innerRef?: React.Ref } /** * This component exposes a callback prop that always returns the DOM node of both functional and class component * children. */ -export default class Ref extends Component { +export default class Ref extends React.Component { static propTypes = { /** * Used to set content when using childrenApi - internal only @@ -22,18 +23,22 @@ export default class Ref extends Component { children: PropTypes.element, /** - * Called when componentDidMount. + * Called when a child component will be mounted or updated. * * @param {HTMLElement} node - Referred node. */ - innerRef: PropTypes.func, + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), } componentDidMount() { - _.invoke(this.props, 'innerRef', findDOMNode(this)) + handleRef(this.props, 'innerRef', findDOMNode(this)) + } + + componentWillUnmount() { + handleRef(this.props, 'innerRef', null) } render() { - return this.props.children && Children.only(this.props.children) + return this.props.children && React.Children.only(this.props.children) } } diff --git a/src/index.ts b/src/index.ts index dec8c33f2d..9a83072f42 100644 --- a/src/index.ts +++ b/src/index.ts @@ -86,6 +86,7 @@ export { RadioGroupItemProps, } from './components/RadioGroup/RadioGroupItem' +export { default as Ref, RefProps } from './components/Ref/Ref' export { default as Segment, SegmentProps } from './components/Segment/Segment' export { default as Status, StatusPropsWithDefaults, StatusProps } from './components/Status/Status' From ba764d7267cac3f42b99298e89eaeb87abd72567 Mon Sep 17 00:00:00 2001 From: olfedias Date: Fri, 9 Nov 2018 16:24:41 +0200 Subject: [PATCH 06/11] docs(handleRef): add JSDoc --- src/lib/handleRef.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/handleRef.ts b/src/lib/handleRef.ts index 09b188dbdb..f2e46a7fde 100644 --- a/src/lib/handleRef.ts +++ b/src/lib/handleRef.ts @@ -1,13 +1,23 @@ import * as React from 'react' +/** A type that ensures that passed prop is defined in props and matches the type. */ type HandleRefProps = { [K in TProp]?: React.Ref } +/** + * The function that correctly handles passing refs. + * + * @param props All components props + * @param propName The name of a ref prop + * @param node A node that should be passed by ref + */ const handleRef =

(props: HandleRefProps, propName: P, node: N) => { const ref: React.Ref = props[propName] if (process.env.NODE_ENV !== 'production') { if (typeof ref === 'string') { - throw new Error("We don't support refs as string") + throw new Error( + 'We do not support refs as string, this is a legacy API and will be likely to be removed in one of the future releases of React.', + ) } } @@ -17,7 +27,7 @@ const handleRef =

(props: HandleRefProps, propName: P } if (typeof ref === 'object') { - // @ts-ignore + // @ts-ignore The `current` property is defined as readonly, however it a valid way ref.current = node } } From 371103d0694fcc2d15048e5d4f8dad6dcf901eb9 Mon Sep 17 00:00:00 2001 From: olfedias Date: Fri, 9 Nov 2018 17:12:02 +0200 Subject: [PATCH 07/11] test(handleRef|Ref): add tests --- test/specs/components/Ref/Ref-test.tsx | 19 ++++++++++++++-- test/specs/lib/handleRef-test.ts | 30 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 test/specs/lib/handleRef-test.ts diff --git a/test/specs/components/Ref/Ref-test.tsx b/test/specs/components/Ref/Ref-test.tsx index 1da88ada72..28705965d5 100644 --- a/test/specs/components/Ref/Ref-test.tsx +++ b/test/specs/components/Ref/Ref-test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' import { shallow, mount } from 'enzyme' +import * as React from 'react' -import { CompositeClass, CompositeFunction, DOMClass, DOMFunction } from './fixtures' import Ref from 'src/components/Ref/Ref' +import { CompositeClass, CompositeFunction, DOMClass, DOMFunction } from './fixtures' const testInnerRef = Component => { const innerRef = jest.fn() @@ -42,5 +42,20 @@ describe('Ref', () => { it('returns node from a class component', () => { testInnerRef(CompositeClass) }) + + it('returns "null" after unmount', () => { + const innerRef = jest.fn() + const wrapper = mount( + + + , + ) + + innerRef.mockClear() + wrapper.unmount() + + expect(innerRef).toHaveBeenCalledTimes(1) + expect(innerRef).toHaveBeenCalledWith(null) + }) }) }) diff --git a/test/specs/lib/handleRef-test.ts b/test/specs/lib/handleRef-test.ts new file mode 100644 index 0000000000..c53f9ad42c --- /dev/null +++ b/test/specs/lib/handleRef-test.ts @@ -0,0 +1,30 @@ +import * as React from 'react' +import handleRef from 'src/lib/handleRef' + +describe('handleRef', () => { + it('throws an error when "ref" is string', () => { + const node = document.createElement('div') + + expect(() => { + handleRef({ ref: 'ref' }, 'ref', node) + }).toThrowError() + }) + + it('calls with node when "ref" is function', () => { + const ref = jest.fn() + const node = document.createElement('div') + + handleRef({ ref }, 'ref', node) + + expect(ref).toBeCalledWith(node) + }) + + it('assigns to "current" when "ref" is object', () => { + const ref = React.createRef() + const node = document.createElement('div') + + handleRef({ ref }, 'ref', node) + + expect(ref.current).toBe(node) + }) +}) From 77cc8d1793af9ee414d605a31750661ccb2e82b6 Mon Sep 17 00:00:00 2001 From: olfedias Date: Wed, 14 Nov 2018 10:23:10 +0100 Subject: [PATCH 08/11] docs(CHANGELOG): add entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f152d91a62..374a73b11b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Set default `chatBehavior` which uses Enter/Esc keys @sophieH29 ([#443](https://github.com/stardust-ui/react/pull/443)) - Add `iconPosition` property to `Input` component @mnajdova ([#442](https://github.com/stardust-ui/react/pull/442)) - Add `color`, `inverted` and `renderContent` props and `content` slot to `Segment` component @Bugaa92 ([#389](https://github.com/stardust-ui/react/pull/389)) +- Export `Ref` component and add `handleRef` util @layershifter ([#459](https://github.com/stardust-ui/react/pull/459)) ### Documentation - Add all missing component descriptions and improve those existing @levithomason ([#400](https://github.com/stardust-ui/react/pull/400)) From 1b7dfa6aa4d04033ab32a101a9b1030c0fc884d2 Mon Sep 17 00:00:00 2001 From: olfedias Date: Wed, 14 Nov 2018 10:29:40 +0100 Subject: [PATCH 09/11] improve description --- src/lib/handleRef.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/handleRef.ts b/src/lib/handleRef.ts index f2e46a7fde..13150b5b7d 100644 --- a/src/lib/handleRef.ts +++ b/src/lib/handleRef.ts @@ -27,7 +27,8 @@ const handleRef =

(props: HandleRefProps, propName: P } if (typeof ref === 'object') { - // @ts-ignore The `current` property is defined as readonly, however it a valid way + // @ts-ignore The `current` property is defined as readonly, however it's a valid way because + // `ref` is a mutable object ref.current = node } } From 9f4845eff2c1c60280da814fefc94d20759b8d19 Mon Sep 17 00:00:00 2001 From: olfedias Date: Thu, 15 Nov 2018 09:43:33 +0100 Subject: [PATCH 10/11] apply changes --- src/components/Ref/Ref.tsx | 4 ++-- src/lib/handleRef.ts | 10 ++-------- test/specs/lib/handleRef-test.ts | 6 +++--- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/components/Ref/Ref.tsx b/src/components/Ref/Ref.tsx index 2aecc565f6..fd2dc23a99 100644 --- a/src/components/Ref/Ref.tsx +++ b/src/components/Ref/Ref.tsx @@ -31,11 +31,11 @@ export default class Ref extends React.Component { } componentDidMount() { - handleRef(this.props, 'innerRef', findDOMNode(this)) + handleRef(this.props.innerRef, findDOMNode(this)) } componentWillUnmount() { - handleRef(this.props, 'innerRef', null) + handleRef(this.props.innerRef, null) } render() { diff --git a/src/lib/handleRef.ts b/src/lib/handleRef.ts index 13150b5b7d..6fcea02285 100644 --- a/src/lib/handleRef.ts +++ b/src/lib/handleRef.ts @@ -1,18 +1,12 @@ import * as React from 'react' -/** A type that ensures that passed prop is defined in props and matches the type. */ -type HandleRefProps = { [K in TProp]?: React.Ref } - /** * The function that correctly handles passing refs. * - * @param props All components props - * @param propName The name of a ref prop + * @param ref An ref object or function * @param node A node that should be passed by ref */ -const handleRef =

(props: HandleRefProps, propName: P, node: N) => { - const ref: React.Ref = props[propName] - +const handleRef = (ref: React.Ref, node: N) => { if (process.env.NODE_ENV !== 'production') { if (typeof ref === 'string') { throw new Error( diff --git a/test/specs/lib/handleRef-test.ts b/test/specs/lib/handleRef-test.ts index c53f9ad42c..aead953a77 100644 --- a/test/specs/lib/handleRef-test.ts +++ b/test/specs/lib/handleRef-test.ts @@ -6,7 +6,7 @@ describe('handleRef', () => { const node = document.createElement('div') expect(() => { - handleRef({ ref: 'ref' }, 'ref', node) + handleRef('ref', node) }).toThrowError() }) @@ -14,7 +14,7 @@ describe('handleRef', () => { const ref = jest.fn() const node = document.createElement('div') - handleRef({ ref }, 'ref', node) + handleRef(ref, node) expect(ref).toBeCalledWith(node) }) @@ -23,7 +23,7 @@ describe('handleRef', () => { const ref = React.createRef() const node = document.createElement('div') - handleRef({ ref }, 'ref', node) + handleRef(ref, node) expect(ref.current).toBe(node) }) From 220df2e79a8796b0c75cae08324a55d96f428a69 Mon Sep 17 00:00:00 2001 From: olfedias Date: Thu, 15 Nov 2018 09:46:02 +0100 Subject: [PATCH 11/11] remove useless empty lines --- test/specs/lib/handleRef-test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/specs/lib/handleRef-test.ts b/test/specs/lib/handleRef-test.ts index aead953a77..fc698353f5 100644 --- a/test/specs/lib/handleRef-test.ts +++ b/test/specs/lib/handleRef-test.ts @@ -15,7 +15,6 @@ describe('handleRef', () => { const node = document.createElement('div') handleRef(ref, node) - expect(ref).toBeCalledWith(node) }) @@ -24,7 +23,6 @@ describe('handleRef', () => { const node = document.createElement('div') handleRef(ref, node) - expect(ref.current).toBe(node) }) })