From 245dbac24e67093db186444e123c77d338dee930 Mon Sep 17 00:00:00 2001 From: Pau Teruel <93.pau.teruel@gmail.com> Date: Fri, 30 Oct 2020 13:43:36 +0100 Subject: [PATCH 1/3] Styled TextField (#185) * feat: create textfield * define type TextField Type - support forwardRef and custom attributes for Shapes and Modes * feat: update textfield to use hoc * feat: add proper types to styled Co-authored-by: Takeichi Kanzaki Cabrera --- .../Form/TextField/TextField.stories.tsx | 64 +++++++++++++++++++ .../Form/TextField/TextField.styled.ts | 53 +++++++++++++++ .../Form/TextField/TextField.test.tsx | 61 ++++++++++++++++++ .../primitives/Form/TextField/TextField.tsx | 48 ++++++++++++++ .../Form/TextField/TextField.types.ts | 39 +++++++++++ .../primitives/Form/TextField/index.tsx | 1 + .../src/components/primitives/index.ts | 1 + 7 files changed, 267 insertions(+) create mode 100644 packages/styled-components/src/components/primitives/Form/TextField/TextField.stories.tsx create mode 100644 packages/styled-components/src/components/primitives/Form/TextField/TextField.styled.ts create mode 100644 packages/styled-components/src/components/primitives/Form/TextField/TextField.test.tsx create mode 100644 packages/styled-components/src/components/primitives/Form/TextField/TextField.tsx create mode 100644 packages/styled-components/src/components/primitives/Form/TextField/TextField.types.ts create mode 100644 packages/styled-components/src/components/primitives/Form/TextField/index.tsx diff --git a/packages/styled-components/src/components/primitives/Form/TextField/TextField.stories.tsx b/packages/styled-components/src/components/primitives/Form/TextField/TextField.stories.tsx new file mode 100644 index 000000000..27b5ff927 --- /dev/null +++ b/packages/styled-components/src/components/primitives/Form/TextField/TextField.stories.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { TextField } from '.'; +import { action } from '@storybook/addon-actions'; +import { boolean, text, select, number } from '@storybook/addon-knobs'; + +export default { + title: 'Primitives/Form/TextField', + component: TextField, +}; + +export const Shapes = () => ( + <> +

TextField shapes

+ {Object.keys(TextField.Shapes).map((shape: any, i) => ( + <> + +
+ + ))} + +); + +export const Modes = () => ( + <> +

TextField Modes

+ {Object.keys(TextField.Modes).map((mode: any) => ( + <> + +
+ + ))} + +); + +export const Disabled = () => ( + <> +

TextField disabled

+ + +); + +export const Invalid = () => ( + <> +

TextField invalid

+ + +); + +export const Playground = () => ( + <> +

Playground

+ + +); diff --git a/packages/styled-components/src/components/primitives/Form/TextField/TextField.styled.ts b/packages/styled-components/src/components/primitives/Form/TextField/TextField.styled.ts new file mode 100644 index 000000000..5029c9285 --- /dev/null +++ b/packages/styled-components/src/components/primitives/Form/TextField/TextField.styled.ts @@ -0,0 +1,53 @@ +import styled from 'styled-components'; +import { getTheme } from 'utils/getTheme'; +import { StyledCssProps } from 'components/Components.styled.types'; +import { withCustomCss } from 'utils/withCustomCss'; + +export const StyledInput = withCustomCss< + StyledCssProps & + React.ClassAttributes & + React.InputHTMLAttributes +>(styled.input` + background-color: ${(props) => getTheme(props).colors.fillFormEnabled}; + position: relative; + display: block; + width: 100%; + padding: 0.75rem; + color: ${(props) => getTheme(props).Typography.colors.dark100}; + z-index: 10; + font-size: ${(props) => getTheme(props).Typography.sm.fontSize.form}; + font-family: ${(props) => getTheme(props).Typography.sm.fontFamily.form}; + appearance: none; + border-width: 1px; + border-style: solid; + border-color: ${(props) => getTheme(props).colors.outlineFormFilled}; + + &.rounded { + border-radius: ${(props) => getTheme(props).Input.borderRadius.rounded}; + } + &.semiRounded { + border-radius: ${(props) => getTheme(props).Input.borderRadius.semiRounded}; + } + &.rectangle { + border-radius: ${(props) => getTheme(props).Input.borderRadius.rectangle}; + } + &.invalid { + border-color: ${(props) => getTheme(props).colors.fillSystemError}; + } + &::placeholder { + color: ${(props) => getTheme(props).colors.outlineFormFilled}; + } + &:focus { + outline: none; + border-color: ${(props) => getTheme(props).colors.outlineFormFocus}; + } + &:disabled { + background-color: ${(props) => getTheme(props).colors.fillFormDisabled}; + border-color: ${(props) => getTheme(props).colors.outlineFormDisabled}; + color: ${(props) => getTheme(props).Typography.colors.dark50}; + } + @media (min-width: ${(props) => getTheme(props).breakpoints.md}) { + font-size: ${(props) => getTheme(props).Typography.md.fontSize.form}; + font-family: ${(props) => getTheme(props).Typography.md.fontFamily.form}; + } +`); diff --git a/packages/styled-components/src/components/primitives/Form/TextField/TextField.test.tsx b/packages/styled-components/src/components/primitives/Form/TextField/TextField.test.tsx new file mode 100644 index 000000000..725e487e9 --- /dev/null +++ b/packages/styled-components/src/components/primitives/Form/TextField/TextField.test.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { TextField } from '.'; + +describe('TextField', () => { + const testId = 'textField'; + it('should render name', () => { + render(); + const input = screen.getByTestId(testId); + expect(input).toHaveAttribute('name', 'foo'); + }); + it('should render type', () => { + render(); + const input = screen.getByTestId(testId); + expect(input).toHaveAttribute('type', 'text'); + }); + it('should render class', () => { + render(); + const input = screen.getByTestId(testId); + expect(input).toHaveClass('foo'); + }); + it('should render aria label', () => { + render(); + const input = screen.getByTestId(testId); + expect(input).toHaveAttribute('aria-label', 'foo'); + }); + it('should render required', () => { + render(); + const input = screen.getByTestId(testId); + expect(input).toBeRequired(); + }); + it('should call onChange', () => { + const mockOnChange = jest.fn(); + render( + + ); + const input = screen.getByTestId(testId); + fireEvent.change(input, { target: { value: 'foo' } }); + expect(mockOnChange).toBeCalledTimes(1); + }); + it('should render disabled', () => { + render(); + const input = screen.getByTestId(testId); + expect(input).toBeDisabled(); + }); + it('should limit max length', () => { + render(); + const input = screen.getByTestId(testId); + expect(input).toHaveAttribute('maxLength', '3'); + }); + it('should render inputMode', () => { + render(); + const input = screen.getByTestId(testId); + expect(input).toHaveAttribute('inputmode', 'numeric'); + }); +}); diff --git a/packages/styled-components/src/components/primitives/Form/TextField/TextField.tsx b/packages/styled-components/src/components/primitives/Form/TextField/TextField.tsx new file mode 100644 index 000000000..f8590eb1e --- /dev/null +++ b/packages/styled-components/src/components/primitives/Form/TextField/TextField.tsx @@ -0,0 +1,48 @@ +import React, { forwardRef } from 'react'; +import cn from 'classnames'; +import { Shapes } from 'components/Components.types'; +import { Props, Modes, Type } from './TextField.types'; +import { StyledInput } from './TextField.styled'; + +export const TextField: Type = forwardRef( + ( + { + className, + shape = Shapes.SemiRounded, + ariaLabel, + name, + placeholder, + onChange, + required, + type = 'text', + disabled = false, + invalid = false, + maxLength, + inputMode, + testId = 'textField', + customCss, + }: Props, + ref + ) => ( + + ) +); + +TextField.Shapes = Shapes; +TextField.Modes = Modes; diff --git a/packages/styled-components/src/components/primitives/Form/TextField/TextField.types.ts b/packages/styled-components/src/components/primitives/Form/TextField/TextField.types.ts new file mode 100644 index 000000000..e4a93b6ea --- /dev/null +++ b/packages/styled-components/src/components/primitives/Form/TextField/TextField.types.ts @@ -0,0 +1,39 @@ +import { Shapes } from 'components/Components.types'; +import { + ChangeEvent, + ForwardRefExoticComponent, + PropsWithoutRef, + RefAttributes, +} from 'react'; +import { CssCustomizableProps } from 'components/Components.types'; + +export type Props = CssCustomizableProps & { + shape?: Shapes; + ariaLabel?: string; + name: string; + placeholder: string; + required?: boolean; + disabled?: boolean; + invalid?: boolean; + type?: string; + maxLength?: number; + onChange?: (event: ChangeEvent) => void; + inputMode?: Modes; +}; + +export type Type = ForwardRefExoticComponent< + PropsWithoutRef & RefAttributes +> & { + Shapes?: typeof Shapes; + Modes?: typeof Modes; +}; + +export enum Modes { + Text = 'text', + Decimal = 'decimal', + Numeric = 'numeric', + Tel = 'tel', + Search = 'search', + Email = 'email', + Url = 'url' +} diff --git a/packages/styled-components/src/components/primitives/Form/TextField/index.tsx b/packages/styled-components/src/components/primitives/Form/TextField/index.tsx new file mode 100644 index 000000000..665fa3cb5 --- /dev/null +++ b/packages/styled-components/src/components/primitives/Form/TextField/index.tsx @@ -0,0 +1 @@ +export * from './TextField'; diff --git a/packages/styled-components/src/components/primitives/index.ts b/packages/styled-components/src/components/primitives/index.ts index 4bd485814..11fd29db0 100644 --- a/packages/styled-components/src/components/primitives/index.ts +++ b/packages/styled-components/src/components/primitives/index.ts @@ -9,6 +9,7 @@ export * from './Dropdown'; export * from './Form/ErrorField'; export * from './Form/RadioButton'; export * from './Form/TextArea'; +export * from './Form/TextField'; export * from './Icon'; export * from './Image'; export * from './Link'; From 5adb2621cfcff2b6dd2e167e97b63c93013e0e61 Mon Sep 17 00:00:00 2001 From: Dawin Valenzuela <55356515+dawinValenzuela@users.noreply.github.com> Date: Fri, 30 Oct 2020 07:47:31 -0500 Subject: [PATCH 2/3] Image customCss Implementation (#186) * adding customcss implementation * aplying suggested changes * Remove unnecessary type, adding custome style for storybook * Implement withCustomCss --- .../primitives/Image/Image.stories.tsx | 6 ++++++ .../components/primitives/Image/Image.styled.ts | 16 +++++++++++----- .../components/primitives/Image/Image.test.tsx | 5 +++++ .../src/components/primitives/Image/Image.tsx | 8 ++++++-- .../components/primitives/Image/Image.types.ts | 4 ++-- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/styled-components/src/components/primitives/Image/Image.stories.tsx b/packages/styled-components/src/components/primitives/Image/Image.stories.tsx index 861a8316b..9b470506d 100644 --- a/packages/styled-components/src/components/primitives/Image/Image.stories.tsx +++ b/packages/styled-components/src/components/primitives/Image/Image.stories.tsx @@ -8,6 +8,11 @@ export default { component: Image, }; +const styles = ` + width: 10%; + cursor: pointer; +`; + export const Playground = () => ( ( ), }} onLoad={action('Image loaded')} + customCss={text('Image styles', styles)} /> ); diff --git a/packages/styled-components/src/components/primitives/Image/Image.styled.ts b/packages/styled-components/src/components/primitives/Image/Image.styled.ts index 3dada6d56..f0bde996a 100644 --- a/packages/styled-components/src/components/primitives/Image/Image.styled.ts +++ b/packages/styled-components/src/components/primitives/Image/Image.styled.ts @@ -1,9 +1,15 @@ import styled from 'styled-components'; -export const StyledSource = styled.source` - user-select: none; -`; +import { StyledCssProps } from 'components/Components.styled.types'; +import { withCustomCss } from 'utils/withCustomCss'; + +type Props = StyledCssProps & { + as?: string; + type?: string; +}; -export const StyledImage = styled.img` +export const StyledImage = withCustomCss< + Props & React.ImgHTMLAttributes +>(styled.img` user-select: none; -`; +`); diff --git a/packages/styled-components/src/components/primitives/Image/Image.test.tsx b/packages/styled-components/src/components/primitives/Image/Image.test.tsx index 182c7cec8..d2e8c5f67 100644 --- a/packages/styled-components/src/components/primitives/Image/Image.test.tsx +++ b/packages/styled-components/src/components/primitives/Image/Image.test.tsx @@ -43,4 +43,9 @@ describe('Image', () => { fireEvent.load(image); expect(mockOnLoad).toHaveBeenCalledTimes(1); }); + it('should render custom CSS as a class', () => { + render(); + const image = screen.getByTestId(imageContentTestId); + expect(image).toHaveClass('custom'); + }); }); diff --git a/packages/styled-components/src/components/primitives/Image/Image.tsx b/packages/styled-components/src/components/primitives/Image/Image.tsx index 1d8256e9d..890b65a96 100644 --- a/packages/styled-components/src/components/primitives/Image/Image.tsx +++ b/packages/styled-components/src/components/primitives/Image/Image.tsx @@ -1,17 +1,20 @@ import React from 'react'; import { Props } from './Image.types'; -import { StyledImage, StyledSource } from './Image.styled'; +import { StyledImage } from './Image.styled'; export const Image = ({ className, imageClassName, + customCss, image: { title, url }, testId = 'image', onLoad, }: Props) => { return ( - void; From 6b49b70955998666a32a16bdfd1919e8feea6436 Mon Sep 17 00:00:00 2001 From: Takeichi Kanzaki Cabrera Date: Mon, 2 Nov 2020 16:29:06 +0100 Subject: [PATCH 3/3] Use typescript config in tests and stories too (#191) * enable typescript configuration in tests and stories - this will fix the issue we have right now with the IDE not auto completing the code in the tests and stories files and not detecting missing required properties * fix types errors in tests and stories --- packages/styled-components/.babelrc | 1 - .../containers/Grid/Grid.stories.tsx | 20 +++++++------- .../primitives/Asset/Asset.test.tsx | 5 ++-- .../src/components/primitives/Asset/Asset.tsx | 4 +-- .../primitives/Asset/Asset.types.ts | 5 ++++ .../ConversationList.stories.tsx | 4 +-- .../ConversationList.test.tsx | 4 +-- .../Form/TextArea/TextArea.stories.tsx | 19 ++++++++++--- .../Form/TextArea/TextArea.test.tsx | 22 ++++++++++----- .../Form/TextField/TextField.stories.tsx | 27 ++++++++++++------- .../Form/TextField/TextField.test.tsx | 22 +++++++++------ .../primitives/Link/Link.stories.tsx | 2 +- packages/styled-components/tsconfig.json | 2 +- 13 files changed, 88 insertions(+), 49 deletions(-) diff --git a/packages/styled-components/.babelrc b/packages/styled-components/.babelrc index 4b69528db..410fae3dc 100644 --- a/packages/styled-components/.babelrc +++ b/packages/styled-components/.babelrc @@ -5,7 +5,6 @@ "module-resolver", { "alias": { - "src": "./src", "components": "./src/components", "utils": "./src/utils", "tests": "./config/jest", diff --git a/packages/styled-components/src/components/containers/Grid/Grid.stories.tsx b/packages/styled-components/src/components/containers/Grid/Grid.stories.tsx index 672ce542a..33ece1266 100644 --- a/packages/styled-components/src/components/containers/Grid/Grid.stories.tsx +++ b/packages/styled-components/src/components/containers/Grid/Grid.stories.tsx @@ -93,7 +93,7 @@ export const TwoColumnsDesktopOneMobileWithImages = () => ( title="Create an Out-of-this-World website with this headline" subtitle="We are right now on the verge of finding out whether there is life elsewhere in the universe, and there are three ways we could find it." asset={{ - contentType: 'image', + contentType: Asset.ContentType.Image, title: 'Image Title', url: 'https://avatars0.githubusercontent.com/u/67131017?s=200&v=4', }} @@ -102,7 +102,7 @@ export const TwoColumnsDesktopOneMobileWithImages = () => ( title="Create an Out-of-this-World website with this headline" subtitle="We are right now on the verge of finding out whether there is life elsewhere in the universe, and there are three ways we could find it." asset={{ - contentType: 'image', + contentType: Asset.ContentType.Image, title: 'Image Title', url: 'https://avatars0.githubusercontent.com/u/67131017?s=200&v=4', }} @@ -111,7 +111,7 @@ export const TwoColumnsDesktopOneMobileWithImages = () => ( title="Create an Out-of-this-World website with this headline" subtitle="We are right now on the verge of finding out whether there is life elsewhere in the universe, and there are three ways we could find it." asset={{ - contentType: 'image', + contentType: Asset.ContentType.Image, title: 'Image Title', url: 'https://avatars0.githubusercontent.com/u/67131017?s=200&v=4', }} @@ -120,7 +120,7 @@ export const TwoColumnsDesktopOneMobileWithImages = () => ( title="Create an Out-of-this-World website with this headline" subtitle="We are right now on the verge of finding out whether there is life elsewhere in the universe, and there are three ways we could find it." asset={{ - contentType: 'image', + contentType: Asset.ContentType.Image, title: 'Image Title', url: 'https://avatars0.githubusercontent.com/u/67131017?s=200&v=4', }} @@ -146,42 +146,42 @@ export const Playground = () => ( asset={{ title: '', url: 'https://avatars0.githubusercontent.com/u/67131017?s=200&v=4', - contentType: 'image', + contentType: Asset.ContentType.Image, }} /> diff --git a/packages/styled-components/src/components/primitives/Asset/Asset.test.tsx b/packages/styled-components/src/components/primitives/Asset/Asset.test.tsx index ee603d206..1be402168 100644 --- a/packages/styled-components/src/components/primitives/Asset/Asset.test.tsx +++ b/packages/styled-components/src/components/primitives/Asset/Asset.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import { Asset } from './Asset'; +import { Asset } from '.'; +import { ContentType } from './Asset.types'; describe('Asset', () => { it('should render an image asset type', () => { @@ -12,7 +13,7 @@ describe('Asset', () => { screen.getByTestId('video'); }); - const givenComponentRendered = (type: Asset.ContentType) => + const givenComponentRendered = (type: ContentType) => render( & { + ContentType?: typeof ContentType; +}; + export enum ContentType { Image = 'image', Video = 'video', diff --git a/packages/styled-components/src/components/primitives/Chat/ConversationList/ConversationList.stories.tsx b/packages/styled-components/src/components/primitives/Chat/ConversationList/ConversationList.stories.tsx index 027d46e8d..87984ae8f 100644 --- a/packages/styled-components/src/components/primitives/Chat/ConversationList/ConversationList.stories.tsx +++ b/packages/styled-components/src/components/primitives/Chat/ConversationList/ConversationList.stories.tsx @@ -24,7 +24,7 @@ export const Conversations = () => ( ( unreadMessages: 0, }, { - id: 2, + id: '2', title: 'John Cena', description: 'Description', message: 'Message', diff --git a/packages/styled-components/src/components/primitives/Chat/ConversationList/ConversationList.test.tsx b/packages/styled-components/src/components/primitives/Chat/ConversationList/ConversationList.test.tsx index ff12fd14a..8a36621c7 100644 --- a/packages/styled-components/src/components/primitives/Chat/ConversationList/ConversationList.test.tsx +++ b/packages/styled-components/src/components/primitives/Chat/ConversationList/ConversationList.test.tsx @@ -4,7 +4,7 @@ import { ConversationList } from '.'; describe('ConversationList', () => { const testId = 'conversationList'; - it('should render classname', () => { + it('should render class name', () => { render(); const conversationList = screen.getByTestId(testId); expect(conversationList).toHaveClass('foo'); @@ -27,7 +27,6 @@ describe('ConversationList', () => { title: 'title', description: 'description', message: 'message', - messageClassName: 'italic', time: 'time', avatarText: 'avatarText', isHighlighted: false, @@ -41,7 +40,6 @@ describe('ConversationList', () => { title: 'title', description: 'description', message: 'message', - messageClassName: 'italic', time: 'time', avatarText: 'avatarText', isHighlighted: false, diff --git a/packages/styled-components/src/components/primitives/Form/TextArea/TextArea.stories.tsx b/packages/styled-components/src/components/primitives/Form/TextArea/TextArea.stories.tsx index 1a650ddce..ec08e9000 100644 --- a/packages/styled-components/src/components/primitives/Form/TextArea/TextArea.stories.tsx +++ b/packages/styled-components/src/components/primitives/Form/TextArea/TextArea.stories.tsx @@ -4,7 +4,7 @@ import { boolean, text, select } from '@storybook/addon-knobs'; export default { title: 'Primitives/Form/TextArea', - component: TextArea + component: TextArea, }; export const Shapes = () => ( @@ -12,7 +12,12 @@ export const Shapes = () => (

TextArea shapes

{Object.keys(TextArea.Shapes).map((shape: any, i) => (
-