@@ -231,7 +330,7 @@ exports[`Storyshots Link Href 1`] = `
exports[`Storyshots Skeleton Circle 1`] = `
@@ -240,27 +339,27 @@ exports[`Storyshots Skeleton Circle 1`] = `
exports[`Storyshots Skeleton Count 1`] = `
Array [
,
,
,
,
,
@@ -269,7 +368,7 @@ Array [
exports[`Storyshots Skeleton Default 1`] = `
diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx
index 7807b52d..5436e0bd 100644
--- a/src/components/Button/Button.stories.tsx
+++ b/src/components/Button/Button.stories.tsx
@@ -25,6 +25,12 @@ Default.args = { children: 'Default' }
export const Disabled = Template.bind({})
Disabled.args = { children: 'Disabled', disabled: true }
+export const Loading = Template.bind({})
+Loading.args = { children: 'Loading', loading: true }
+
+export const Pending = Template.bind({})
+Pending.args = { children: 'Pending', pending: true }
+
export const Primary = Template.bind({})
Primary.args = { children: 'Primary', primary: true }
diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx
index db8f2994..b36b190e 100644
--- a/src/components/Button/Button.test.tsx
+++ b/src/components/Button/Button.test.tsx
@@ -1,5 +1,6 @@
-import { Button as AntDButton } from 'antd'
import React from 'react'
+import Skeleton from '../Skeleton'
+import { Button as AntDButton, Spin } from 'antd'
import Button, { ButtonProps } from '.'
import { shallow, ShallowWrapper } from 'enzyme'
@@ -35,6 +36,40 @@ describe('Button', () => {
})
})
+describe('Loading Button', () => {
+ beforeEach(() => {
+ wrapper = shallow(
+
+ )
+ })
+ it('renders a skeleton', () => {
+ expect(wrapper.find(Skeleton)).toHaveLength(1)
+ })
+
+ it('renders a skeleton with a default width of 75 and height of 32', () => {
+ expect(wrapper.find(Skeleton).props().height).toBe(32)
+ expect(wrapper.find(Skeleton).props().width).toBe(75)
+ })
+
+ it('renders a skeleton with the correct custom dimensions', () => {
+ wrapper = shallow(
+
+ )
+
+ expect(wrapper.find(Skeleton).props().height).toBe(50)
+ expect(wrapper.find(Skeleton).props().width).toBe(200)
+ })
+})
+
describe('Disabled Button', () => {
it('has correct prop "disabled" and correct class "disabled"', () => {
wrapper = shallow(
@@ -45,3 +80,21 @@ describe('Disabled Button', () => {
expect(wrapper.props().disabled).toBeTruthy()
})
})
+
+describe('Pending Button', () => {
+ beforeEach(() => {
+ wrapper = shallow(
+
+ )
+ })
+
+ it('renders a Spin component', () => {
+ expect(wrapper.find(Spin)).toHaveLength(1)
+ })
+
+ it('automatically disables the button', () => {
+ expect(wrapper.find(AntDButton).props().disabled).toBeTruthy()
+ })
+})
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
index badeeae6..d15340cc 100644
--- a/src/components/Button/index.tsx
+++ b/src/components/Button/index.tsx
@@ -1,7 +1,10 @@
import 'antd/lib/button/style/index.css'
-import { Button as AntDButton } from 'antd'
+import 'antd/lib/spin/style/index.css'
import { ButtonProps as AntDButtonProps } from 'antd/es/button'
import classnames from 'classnames'
+import { LoadingOutlined } from '@ant-design/icons'
+import Skeleton from '../Skeleton'
+import { Button as AntDButton, Spin } from 'antd'
import React, { FC, ReactNode } from 'react'
export interface ButtonProps {
@@ -21,6 +24,22 @@ export interface ButtonProps {
* Adds the disabled attribute and styles (opacity, gray scale filter, no pointer events).
*/
disabled?: boolean
+ /**
+ * Renders a skeleton for the button.
+ */
+ loading?: boolean
+ /**
+ * Renders an animated loading icon next to the children.
+ */
+ pending?: boolean
+ /**
+ * Skeleton loader height.
+ */
+ skeletonHeight?: number
+ /**
+ * Skeleton loader width.
+ */
+ skeletonWidth?: number
/**
* Array of classes to pass to button.
*/
@@ -29,19 +48,38 @@ export interface ButtonProps {
const Button: FC
= ({
children,
+ classes = [],
+ disabled = false,
+ loading = false,
onClick,
+ pending = false,
primary = false,
- disabled = false,
- classes = []
+ skeletonHeight = 32,
+ skeletonWidth = 75
}: ButtonProps) => {
const antDProps: AntDButtonProps = {
className: classnames(classes),
- disabled,
+ disabled: pending || disabled,
onClick,
type: primary ? 'primary' : 'default'
}
- return {children}
+ return loading ? (
+
+ ) : (
+
+ {pending && (
+
+
+ }
+ />
+
+ )}
+ {children}
+
+ )
}
export default Button
diff --git a/src/components/Form/FieldContext.ts b/src/components/Form/FieldContext.ts
new file mode 100644
index 00000000..11674868
--- /dev/null
+++ b/src/components/Form/FieldContext.ts
@@ -0,0 +1,13 @@
+import { createContext } from 'react'
+import { FieldValues } from 'react-hook-form/dist/types/form'
+import { SubmitHandler } from 'react-hook-form'
+
+export interface FieldContextProps {
+ initialValues: FieldValues
+ loading: boolean
+ onSubmit: SubmitHandler
+}
+
+const FieldContext = createContext({} as FieldContextProps)
+
+export default FieldContext
diff --git a/src/components/Form/FieldLabel/FieldLabel.test.tsx b/src/components/Form/FieldLabel/FieldLabel.test.tsx
new file mode 100644
index 00000000..91074ded
--- /dev/null
+++ b/src/components/Form/FieldLabel/FieldLabel.test.tsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import Skeleton from '../../Skeleton'
+import FieldLabel, { FieldLabelProps } from './index'
+import { mount, shallow, ShallowWrapper } from 'enzyme'
+
+let wrapper: ShallowWrapper
+
+const MOCK_LABEL = 'Field Label'
+
+const baseMockProps = {
+ label: MOCK_LABEL
+}
+
+beforeEach(() => {
+ wrapper = shallow()
+})
+
+describe('Field Label', () => {
+ it('renders', () => {
+ expect(wrapper).toHaveLength(1)
+ })
+
+ it('correctly renders the label', () => {
+ expect(wrapper.text()).toContain(MOCK_LABEL)
+ })
+
+ it('applies the correct class if required is passed as true', () => {
+ const fieldLabel = mount()
+
+ expect(fieldLabel.getDOMNode().classList.toString()).toContain(
+ 'required'
+ )
+ })
+
+ describe('loading', () => {
+ beforeEach(() => {
+ wrapper = shallow()
+ })
+
+ it('renders a skeleton if loading prop is passed as true', () => {
+ expect(wrapper.find(Skeleton)).toHaveLength(1)
+ })
+
+ it('passes a default skeleton width of 100', () => {
+ expect(wrapper.find(Skeleton).props().width).toBe(100)
+ })
+
+ it('passes the correct width to Skeleton if one is provided', () => {
+ wrapper = shallow(
+
+ )
+
+ expect(wrapper.find(Skeleton).props().width).toBe(300)
+ })
+ })
+})
diff --git a/src/components/Form/FieldLabel/index.tsx b/src/components/Form/FieldLabel/index.tsx
new file mode 100644
index 00000000..68cb2e3a
--- /dev/null
+++ b/src/components/Form/FieldLabel/index.tsx
@@ -0,0 +1,49 @@
+import cn from 'classnames'
+import { createUseStyles } from 'react-jss'
+import { fontSizeRegular } from '../../assets/styleguide'
+import Skeleton from '../../Skeleton'
+import React, { FC } from 'react'
+
+const useStyles = createUseStyles({
+ container: {
+ fontSize: fontSizeRegular,
+ paddingBottom: 5
+ },
+ required: {
+ '&::after': {
+ color: 'red',
+ // eslint-disable-next-line quotes
+ content: "'*'",
+ paddingLeft: '5px'
+ }
+ }
+})
+
+export interface FieldLabelProps {
+ label: string
+ loading?: boolean
+ required?: boolean
+ skeletonWidth?: number
+}
+
+const FieldLabel: FC = ({
+ label,
+ loading = false,
+ required = false,
+ skeletonWidth = 100
+}: FieldLabelProps) => {
+ const classes = useStyles()
+
+ return (
+
+ {loading ? : label}
+
+ )
+}
+
+export default FieldLabel
diff --git a/src/components/Form/Form.stories.tsx b/src/components/Form/Form.stories.tsx
new file mode 100644
index 00000000..e3cf4fcf
--- /dev/null
+++ b/src/components/Form/Form.stories.tsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import Form, { FormProps } from './index'
+import { Meta, Story } from '@storybook/react/types-6-0'
+
+export default {
+ argTypes: {
+ initialValues: { control: { disable: true } },
+ onSubmit: { control: { disable: true } }
+ },
+ component: Form,
+ title: 'Form'
+} as Meta
+
+interface UserModel {
+ firstName: string
+ lastName?: string
+}
+
+const Template: Story> = (args: FormProps) => (
+
+
+
+
+)
+
+export const Default = Template.bind({})
diff --git a/src/components/Form/Form.test.tsx b/src/components/Form/Form.test.tsx
new file mode 100644
index 00000000..d9fdb0b0
--- /dev/null
+++ b/src/components/Form/Form.test.tsx
@@ -0,0 +1,75 @@
+import FieldContext from './FieldContext'
+import FormButton from './FormButton'
+import React from 'react'
+import Form, { FormProps } from './index'
+import { mount, shallow, ShallowWrapper } from 'enzyme'
+
+jest.mock('react-hook-form', () => ({
+ ...jest.requireActual('react-hook-form'),
+ useForm: () => ({
+ handleSubmit: jest.fn(),
+ reset: mockReset
+ })
+}))
+
+interface MockForm {
+ foo: string
+}
+
+let wrapper: ShallowWrapper>
+
+const mockInitialValues = { foo: 'bar' }
+const mockOnSubmit = jest.fn()
+const mockReset = jest.fn()
+
+beforeEach(() => {
+ wrapper = shallow(
+
+ )
+})
+
+describe('Form', () => {
+ it('renders', () => {
+ expect(wrapper).toHaveLength(1)
+ })
+
+ it('renders children properly', () => {
+ expect(wrapper.find(FormButton)).toHaveLength(1)
+ })
+
+ it('passes the correct props to FieldContext provider', () => {
+ expect(wrapper.find(FieldContext.Provider).props().value).toMatchObject(
+ {
+ initialValues: mockInitialValues,
+ loading: false,
+ onSubmit: mockOnSubmit
+ }
+ )
+ })
+
+ it('correctly defaults initial values to empty object if none is passed in', () => {
+ wrapper = shallow(
+
+ )
+
+ expect(
+ wrapper.find(FieldContext.Provider).props().value
+ ).toMatchObject({ initialValues: {} })
+ })
+
+ it('correctly updates initial values', () => {
+ const form = mount(
+
+ )
+
+ form.setProps({ initialValues: mockInitialValues })
+
+ expect(mockReset).toHaveBeenCalledWith(mockInitialValues)
+ })
+})
diff --git a/src/components/Form/FormButton/FormButton.test.tsx b/src/components/Form/FormButton/FormButton.test.tsx
new file mode 100644
index 00000000..9caa0d5e
--- /dev/null
+++ b/src/components/Form/FormButton/FormButton.test.tsx
@@ -0,0 +1,46 @@
+import Button from '../../Button'
+import FieldContext from '../FieldContext'
+import React from 'react'
+import FormButton, { FormButtonProps } from './index'
+import { mount, ReactWrapper } from 'enzyme'
+
+jest.mock('react-hook-form', () => ({
+ ...jest.requireActual('react-hook-form'),
+ useFormContext: () => ({
+ handleSubmit: (onSubmit: Function) => onSubmit()
+ })
+}))
+
+let wrapper: ReactWrapper
+
+const mockOnSubmit = jest.fn()
+
+beforeEach(() => {
+ wrapper = mount(
+
+
+
+ )
+})
+
+afterEach(() => {
+ jest.resetAllMocks()
+})
+
+describe('FormButton', () => {
+ it('renders', () => {
+ expect(wrapper).toHaveLength(1)
+ })
+
+ it('calls onSubmit when clicked', () => {
+ wrapper.simulate('click')
+
+ expect(mockOnSubmit).toHaveBeenCalledTimes(1)
+ })
+
+ it('correctly passes loading from field context', () => {
+ expect(wrapper.find(Button).props().loading).toBe(true)
+ })
+})
diff --git a/src/components/Form/FormButton/index.tsx b/src/components/Form/FormButton/index.tsx
new file mode 100644
index 00000000..ca66baa2
--- /dev/null
+++ b/src/components/Form/FormButton/index.tsx
@@ -0,0 +1,22 @@
+import FieldContext from '../FieldContext'
+import { useFormContext } from 'react-hook-form'
+import Button, { ButtonProps } from '../../Button'
+import React, { FC, useContext } from 'react'
+
+export type FormButtonProps = Omit<
+ ButtonProps,
+ 'loading' | 'onClick' | 'children'
+>
+
+const FormButton: FC = (props: FormButtonProps) => {
+ const { handleSubmit } = useFormContext()
+ const { loading, onSubmit } = useContext(FieldContext)
+
+ return (
+
+ )
+}
+
+export default FormButton
diff --git a/src/components/Form/FormInput/FormInput.test.tsx b/src/components/Form/FormInput/FormInput.test.tsx
new file mode 100644
index 00000000..dbffac6f
--- /dev/null
+++ b/src/components/Form/FormInput/FormInput.test.tsx
@@ -0,0 +1,64 @@
+import { Controller } from 'react-hook-form'
+import FieldContext from '../FieldContext'
+import React from 'react'
+import FormInput, { FormInputProps } from './index'
+import { mount, ReactWrapper } from 'enzyme'
+
+jest.mock('react-hook-form', () => ({
+ ...jest.requireActual('react-hook-form'),
+ Controller: () => ,
+ useFormContext: () => ({
+ control: jest.fn(),
+ errors: () => ({ foo: true })
+ })
+}))
+
+let wrapper: ReactWrapper
+
+const mockOnSubmit = jest.fn()
+
+beforeEach(() => {
+ wrapper = mount(
+
+
+
+ )
+})
+
+afterEach(() => {
+ jest.resetAllMocks()
+})
+
+describe('FormInput', () => {
+ it('renders', () => {
+ expect(wrapper).toHaveLength(1)
+ })
+
+ it('correctly passes a default value from initial values if it exists', () => {
+ expect(wrapper.find(Controller).props().defaultValue).toEqual('bar')
+ })
+
+ it('correctly passes validation rules if required', () => {
+ wrapper = mount(
+
+
+
+ )
+
+ expect(wrapper.find(Controller).props().rules).toMatchObject({
+ required: true
+ })
+ })
+})
diff --git a/src/components/Form/FormInput/index.tsx b/src/components/Form/FormInput/index.tsx
new file mode 100644
index 00000000..01a67727
--- /dev/null
+++ b/src/components/Form/FormInput/index.tsx
@@ -0,0 +1,67 @@
+import FieldLabel from '../FieldLabel'
+import { Controller, useFormContext, ValidationRules } from 'react-hook-form'
+import FieldContext, { FieldContextProps } from '../FieldContext'
+import Input, { InputProps } from '../../Input'
+import React, { FC, useContext } from 'react'
+
+interface BaseFieldProps {
+ label?: string
+ labelSkeletonWidth?: number
+ name: string
+ required?: boolean
+ rules?: ValidationRules
+}
+
+export interface FormInputProps
+ extends BaseFieldProps,
+ Omit {}
+
+const FormInput: FC = ({
+ label,
+ labelSkeletonWidth,
+ name,
+ required,
+ rules = {},
+ ...rest
+}: FormInputProps) => {
+ const { control, errors } = useFormContext()
+ const { initialValues, loading } = useContext(
+ FieldContext
+ )
+
+ if (required) {
+ rules.required = true
+ }
+
+ const defaultValue = (initialValues[name] as string) || ''
+
+ return (
+
+ {label && (
+
+ )}
+ (
+
+ )}
+ rules={rules}
+ />
+
+ )
+}
+
+export default FormInput
diff --git a/src/components/Form/index.tsx b/src/components/Form/index.tsx
new file mode 100644
index 00000000..300de4df
--- /dev/null
+++ b/src/components/Form/index.tsx
@@ -0,0 +1,55 @@
+import { createUseStyles } from 'react-jss'
+import FieldContext from './FieldContext'
+import { FieldValues } from 'react-hook-form/dist/types/form'
+import FormButton from './FormButton'
+import FormInput from './FormInput'
+import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'
+import React, { ReactNode, useEffect } from 'react'
+
+const useStyles = createUseStyles({
+ container: {
+ '& >div:not(:last-child)': {
+ paddingBottom: 15
+ }
+ }
+})
+
+export interface FormProps {
+ children: ReactNode
+ initialValues?: Model
+ loading?: boolean
+ onSubmit: SubmitHandler
+}
+
+function Form({
+ children,
+ initialValues = {} as Model,
+ loading = false,
+ onSubmit
+}: FormProps) {
+ const classes = useStyles()
+ const methods = useForm()
+
+ const { handleSubmit, reset } = methods
+
+ useEffect(() => {
+ reset(initialValues)
+ }, [initialValues, reset])
+
+ return (
+
+
+
+ )
+}
+
+Form.Button = FormButton
+Form.Input = FormInput
+
+export default Form
diff --git a/src/components/InputField/InputField.stories.tsx b/src/components/Input/Input.stories.tsx
similarity index 74%
rename from src/components/InputField/InputField.stories.tsx
rename to src/components/Input/Input.stories.tsx
index 0e1e1078..7d37be79 100644
--- a/src/components/InputField/InputField.stories.tsx
+++ b/src/components/Input/Input.stories.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import InputField, { InputFieldProps } from './index'
+import Input, { InputProps } from './index'
import { Meta, Story } from '@storybook/react/types-6-0'
export default {
@@ -8,11 +8,11 @@ export default {
fieldLabel: { defaultValue: 'Field Label' },
value: { control: { disable: true } }
},
- component: InputField,
- title: 'InputField'
+ component: Input,
+ title: 'Input'
} as Meta
-const Template: Story = args =>
+const Template: Story = args =>
export const Default = Template.bind({})
diff --git a/src/components/InputField/InputField.test.tsx b/src/components/Input/Input.test.tsx
similarity index 50%
rename from src/components/InputField/InputField.test.tsx
rename to src/components/Input/Input.test.tsx
index a6c70961..d72fe041 100644
--- a/src/components/InputField/InputField.test.tsx
+++ b/src/components/Input/Input.test.tsx
@@ -1,80 +1,64 @@
-import { Input } from 'antd'
+import { Input as AntDInput } from 'antd'
+import Input from './index'
import React from 'react'
import Skeleton from '../Skeleton'
-import InputField, { InputFieldProps } from './index'
import { mount, ReactWrapper, shallow } from 'enzyme'
let wrapper: ReactWrapper
-const mockProps: InputFieldProps = {
- fieldLabel: 'Field Label'
-}
-
beforeEach(() => {
- wrapper = mount()
+ wrapper = mount()
})
-describe('InputField', () => {
+describe('Input', () => {
it('renders', () => {
- const inputField = wrapper.find(InputField)
-
- expect(inputField).toHaveLength(1)
- })
+ const input = wrapper.find(Input)
- it('renders a label if one exists', () => {
- const inputField = wrapper.find(InputField)
-
- expect(inputField.text()).toContain('Field Label')
+ expect(input).toHaveLength(1)
})
it('throws an error if value is passed without an onClick', () => {
- expect(() => shallow()).toThrow()
+ expect(() => shallow()).toThrow()
})
it('should pass onChange and value to the input component if the props exist', () => {
const mockOnChange = jest.fn()
- wrapper = mount()
+ wrapper = mount()
- expect(wrapper.find(Input).props().onChange).toEqual(mockOnChange)
- expect(wrapper.find(Input).props().value).toEqual('abc')
+ expect(wrapper.find(AntDInput).props().onChange).toEqual(mockOnChange)
+ expect(wrapper.find(AntDInput).props().value).toEqual('abc')
})
it('correctly passes the disabled prop', () => {
- wrapper = mount()
+ wrapper = mount()
- expect(wrapper.find(Input).props().disabled).toBeTruthy()
+ expect(wrapper.find(AntDInput).props().disabled).toBeTruthy()
})
it('correctly passes the placeholder prop', () => {
- wrapper = mount()
+ wrapper = mount()
- expect(wrapper.find(Input).props().placeholder).toEqual('Testing')
+ expect(wrapper.find(AntDInput).props().placeholder).toEqual('Testing')
})
describe('type', () => {
it('defaults to type text if no type is specified', () => {
- expect(wrapper.find(Input).props().type).toEqual('text')
+ expect(wrapper.find(AntDInput).props().type).toEqual('text')
})
it('correctly passes the input type prop', () => {
- wrapper = mount()
+ wrapper = mount()
- expect(wrapper.find(Input).props().type).toEqual('password')
+ expect(wrapper.find(AntDInput).props().type).toEqual('password')
})
})
describe('loading', () => {
it('renders a loading skeleton', () => {
- wrapper = mount()
+ wrapper = mount()
expect(wrapper.find(Skeleton)).toHaveLength(1)
})
-
- it('renders a loading skeleton for the label if there is one', () => {
- wrapper = mount()
-
- expect(wrapper.find(Skeleton)).toHaveLength(2)
- })
})
describe('fullWidth', () => {
@@ -94,7 +78,7 @@ describe('InputField', () => {
})
it('renders a container that will span the width of its parent container if set to true', () => {
- wrapper = mount(, {
+ wrapper = mount(, {
attachTo: document.getElementById('container')
})
@@ -104,7 +88,7 @@ describe('InputField', () => {
})
it('does not render a container that will span the width of its parent container by default', () => {
- wrapper = mount(, {
+ wrapper = mount(, {
attachTo: document.getElementById('container')
})
@@ -114,21 +98,11 @@ describe('InputField', () => {
})
})
- describe('required', () => {
- it('passes the correct required class to field label container', () => {
- wrapper = mount()
-
- expect(wrapper.getDOMNode().children[0].className).toContain(
- 'required'
- )
- })
- })
-
describe('error', () => {
it('passes the correct error class to input', () => {
- wrapper = mount()
+ wrapper = mount()
- expect(wrapper.find(Input).hasClass(/error-*/)).toBeTruthy()
+ expect(wrapper.find(AntDInput).hasClass(/error-*/)).toBeTruthy()
})
})
})
diff --git a/src/components/InputField/index.tsx b/src/components/Input/index.tsx
similarity index 68%
rename from src/components/InputField/index.tsx
rename to src/components/Input/index.tsx
index 6da0bdba..94a99a48 100644
--- a/src/components/InputField/index.tsx
+++ b/src/components/Input/index.tsx
@@ -1,7 +1,7 @@
import 'antd/lib/input/style/index.css'
+import { Input as AntDInput } from 'antd'
import cn from 'classnames'
import { createUseStyles } from 'react-jss'
-import { Input } from 'antd'
import Skeleton from '../Skeleton'
import React, { FC } from 'react'
@@ -14,8 +14,6 @@ const useStyles = createUseStyles({
}
},
container: {
- display: 'flex',
- flexDirection: 'column',
width: props => (props.fullWidth ? '100%' : '300px')
},
error: {
@@ -25,41 +23,23 @@ const useStyles = createUseStyles({
input: {
border: '1px solid #DEDEDF',
borderRadius: '4px',
- padding: '8.5px 14px'
- },
- label: {
- fontSize: '14px',
- paddingBottom: '5px'
- },
- required: {
- '&::after': {
- color: 'red',
- // eslint-disable-next-line quotes
- content: "'*'",
- paddingLeft: '5px'
- }
+ padding: '6px 14px'
}
})
-const InputFieldSkeleton: FC = (props: InputFieldProps) => {
- const { fieldLabel } = props
+const InputSkeleton: FC = (props: InputProps) => {
const classes = useStyles(props)
return (
- {fieldLabel && (
-
-
-
- )}
-
+
)
}
-export interface InputFieldProps {
+export interface InputProps {
/**
* Array of classes to pass to input
* @default []
@@ -75,10 +55,6 @@ export interface InputFieldProps {
* @default false
*/
error?: boolean
- /**
- * Adds a label above the input
- */
- fieldLabel?: string
/**
* Whether or not input spans the full width of the parent container
* @default false
@@ -98,11 +74,6 @@ export interface InputFieldProps {
* Describes expected value of input
*/
placeholder?: string
- /**
- * Adds an asterisk to the label that indicates field is required
- * @default false
- */
- required?: boolean
/**
* Type of input (ex: text, password)
* @default text
@@ -114,16 +85,14 @@ export interface InputFieldProps {
value?: string
}
-const InputField: FC = (props: InputFieldProps) => {
+const Input: FC = (props: InputProps) => {
const {
classes = [],
disabled = false,
onChange,
error = false,
- fieldLabel = '',
loading = false,
placeholder = '',
- required = false,
type = 'text',
value
} = props
@@ -151,20 +120,10 @@ const InputField: FC = (props: InputFieldProps) => {
}
return loading ? (
-
+
) : (
- {fieldLabel && (
-
- {fieldLabel}
-
- )}
-
= (props: InputFieldProps) => {
)
}
-export default InputField
+export default Input
diff --git a/src/components/Link/Link.test.tsx b/src/components/Link/Link.test.tsx
index ecf09c17..c7e19518 100644
--- a/src/components/Link/Link.test.tsx
+++ b/src/components/Link/Link.test.tsx
@@ -65,4 +65,10 @@ describe('Link', () => {
mockProps.target
)
})
+
+ it('has a default target of _self if none is passed in', () => {
+ wrapper = mount(
Test)
+
+ expect(wrapper.getDOMNode().getAttribute('target')).toBe('_self')
+ })
})
diff --git a/src/components/assets/styleguide.ts b/src/components/assets/styleguide.ts
index c9d361ac..bb784468 100644
--- a/src/components/assets/styleguide.ts
+++ b/src/components/assets/styleguide.ts
@@ -1 +1,2 @@
+export const fontSizeRegular = '14px'
export const linkColor = '#1EA7FD'
diff --git a/src/components/index.ts b/src/components/index.ts
index be529c46..9ec6464c 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,5 +1,6 @@
export { default as Button } from './Button'
-export { default as InputField } from './InputField'
+export { default as Form } from './Form'
+export { default as Input } from './Input'
export { default as Icon } from './Icon'
export { default as Link } from './Link'
export { default as Skeleton } from './Skeleton'
From c562cebc3e69354a8ea4237371af15b5e85bbe77 Mon Sep 17 00:00:00 2001
From: Parth Shah
Date: Wed, 26 Aug 2020 21:45:54 -0700
Subject: [PATCH 31/50] v0.2.4 -> v0.2.5
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 7c6c44cc..322af3f9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@dassana-io/web-components",
- "version": "0.2.4",
+ "version": "0.2.5",
"publishConfig": {
"registry": "https://npm.pkg.github.com/dassana-io"
},
From 31e099ffe5d48e5fb8a44e0845ec1236d4d4e57f Mon Sep 17 00:00:00 2001
From: Nancy <68706811+nancy-dassana@users.noreply.github.com>
Date: Wed, 2 Sep 2020 15:40:15 -0700
Subject: [PATCH 32/50] feat #71 - Notification component (#72)
* feat #71 - Notification component
---
package.json | 2 +-
rollup.config.ts | 2 +-
src/__snapshots__/storybook.test.ts.snap | 52 +++++++++++++++++++
.../Notification/Notification.stories.tsx | 42 +++++++++++++++
.../Notification/Notification.test.ts | 24 +++++++++
src/components/Notification/index.ts | 8 +++
src/components/index.ts | 1 +
7 files changed, 129 insertions(+), 2 deletions(-)
create mode 100644 src/components/Notification/Notification.stories.tsx
create mode 100644 src/components/Notification/Notification.test.ts
create mode 100644 src/components/Notification/index.ts
diff --git a/package.json b/package.json
index 322af3f9..4af1887d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@dassana-io/web-components",
- "version": "0.2.5",
+ "version": "0.2.6",
"publishConfig": {
"registry": "https://npm.pkg.github.com/dassana-io"
},
diff --git a/rollup.config.ts b/rollup.config.ts
index 8175ef22..86328345 100644
--- a/rollup.config.ts
+++ b/rollup.config.ts
@@ -21,7 +21,7 @@ export default {
assetFileNames
}
],
- external: ['react'],
+ external: ['antd', 'react'],
plugins: [
resolve(),
commonjs(),
diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap
index 85a1a102..761c2386 100644
--- a/src/__snapshots__/storybook.test.ts.snap
+++ b/src/__snapshots__/storybook.test.ts.snap
@@ -328,6 +328,58 @@ exports[`Storyshots Link Href 1`] = `
`;
+exports[`Storyshots Notifications Error 1`] = `
+
+`;
+
+exports[`Storyshots Notifications Info 1`] = `
+
+`;
+
+exports[`Storyshots Notifications Success 1`] = `
+
+`;
+
+exports[`Storyshots Notifications Warning 1`] = `
+
+`;
+
exports[`Storyshots Skeleton Circle 1`] = `
= args => (
+
+)
+
+export const Error = Template.bind({})
+Error.args = {
+ children: 'Error',
+ onClick: () =>
+ generateNotification('error', 'Message for error notification')
+}
+
+export const Info = Template.bind({})
+Info.args = {
+ children: 'Info',
+ onClick: () => generateNotification('info', 'Message for info notification')
+}
+
+export const Success = Template.bind({})
+Success.args = {
+ children: 'Success',
+ onClick: () =>
+ generateNotification('success', 'Message for success notification')
+}
+
+export const Warning = Template.bind({})
+Warning.args = {
+ children: 'Warning',
+ onClick: () =>
+ generateNotification('warning', 'Message for warning notification')
+}
diff --git a/src/components/Notification/Notification.test.ts b/src/components/Notification/Notification.test.ts
new file mode 100644
index 00000000..43684b40
--- /dev/null
+++ b/src/components/Notification/Notification.test.ts
@@ -0,0 +1,24 @@
+import { generateNotification } from './index'
+import { notification } from 'antd'
+
+describe('Notification', () => {
+ it('should render a notification with the message content', () => {
+ generateNotification('error', 'something')
+
+ const notification = document.querySelectorAll('.ant-notification')[0]
+
+ expect(notification).toBeDefined()
+ expect(notification.textContent).toBe('something')
+ })
+
+ it('should use the notification api provided by AntD', () => {
+ jest.spyOn(notification, 'error')
+
+ const mockErrorNotification = 'Fake Error Notification'
+ generateNotification('error', mockErrorNotification)
+
+ expect(notification.error).toHaveBeenCalledWith({
+ message: mockErrorNotification
+ })
+ })
+})
diff --git a/src/components/Notification/index.ts b/src/components/Notification/index.ts
new file mode 100644
index 00000000..b179e9c2
--- /dev/null
+++ b/src/components/Notification/index.ts
@@ -0,0 +1,8 @@
+import 'antd/lib/notification/style/index.css'
+import { IconType } from 'antd/lib/notification'
+import { notification } from 'antd'
+
+export const generateNotification = (type: IconType, message: string): void =>
+ notification[type]({
+ message
+ })
diff --git a/src/components/index.ts b/src/components/index.ts
index 9ec6464c..c6abba67 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -3,6 +3,7 @@ export { default as Form } from './Form'
export { default as Input } from './Input'
export { default as Icon } from './Icon'
export { default as Link } from './Link'
+export * from './Notification'
export { default as Skeleton } from './Skeleton'
export { default as Tag } from './Tag'
export { default as Toggle } from './Toggle'
From 06d095a222d074d8ffd0e1d65538547129568477 Mon Sep 17 00:00:00 2001
From: Nancy <68706811+nancy-dassana@users.noreply.github.com>
Date: Tue, 8 Sep 2020 11:45:02 -0700
Subject: [PATCH 33/50] feat #76 - Refactor Form.Button to be Form.SubmitButton
(#77)
* feat #76 - Refactor Form.Button to be Form.SubmitButton
Closes #76
* feat #77 - Fix failing snapshot test
Co-authored-by: github-actions
---
package.json | 2 +-
src/__snapshots__/storybook.test.ts.snap | 2 +-
src/components/Form/Form.stories.tsx | 2 +-
src/components/Form/Form.test.tsx | 16 +++--
.../Form/FormButton/FormButton.test.tsx | 46 ------------
src/components/Form/FormButton/index.tsx | 22 ------
.../FormSubmitButton.test.tsx | 70 +++++++++++++++++++
.../Form/FormSubmitButton/index.tsx | 23 ++++++
src/components/Form/index.tsx | 4 +-
9 files changed, 109 insertions(+), 78 deletions(-)
delete mode 100644 src/components/Form/FormButton/FormButton.test.tsx
delete mode 100644 src/components/Form/FormButton/index.tsx
create mode 100644 src/components/Form/FormSubmitButton/FormSubmitButton.test.tsx
create mode 100644 src/components/Form/FormSubmitButton/index.tsx
diff --git a/package.json b/package.json
index 4af1887d..14760d53 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@dassana-io/web-components",
- "version": "0.2.6",
+ "version": "0.2.7",
"publishConfig": {
"registry": "https://npm.pkg.github.com/dassana-io"
},
diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap
index 761c2386..1dde3846 100644
--- a/src/__snapshots__/storybook.test.ts.snap
+++ b/src/__snapshots__/storybook.test.ts.snap
@@ -196,7 +196,7 @@ exports[`Storyshots Form Default 1`] = `