From 192e27d85df48458bb1d20c179ccdb032c293e6b Mon Sep 17 00:00:00 2001 From: Peter Hellstrand Date: Thu, 16 May 2024 17:44:32 +0200 Subject: [PATCH] feat(ffe-grid-react): rewrite to ts BREAKING CHANGE: does not throw for invalid colors --- packages/ffe-grid-react/README.md | 5 - packages/ffe-grid-react/package.json | 2 +- packages/ffe-grid-react/src/Grid.js | 29 ---- packages/ffe-grid-react/src/Grid.spec.js | 51 ------ packages/ffe-grid-react/src/Grid.spec.tsx | 61 +++++++ packages/ffe-grid-react/src/Grid.tsx | 28 ++++ packages/ffe-grid-react/src/GridCol.js | 158 ------------------ packages/ffe-grid-react/src/GridCol.spec.js | 154 ----------------- packages/ffe-grid-react/src/GridCol.spec.tsx | 150 +++++++++++++++++ packages/ffe-grid-react/src/GridCol.tsx | 121 ++++++++++++++ packages/ffe-grid-react/src/GridRow.js | 97 ----------- packages/ffe-grid-react/src/GridRow.spec.js | 118 ------------- packages/ffe-grid-react/src/GridRow.spec.tsx | 79 +++++++++ packages/ffe-grid-react/src/GridRow.tsx | 60 +++++++ .../ffe-grid-react/src/background-colors.js | 29 ---- packages/ffe-grid-react/src/index.d.ts | 82 --------- packages/ffe-grid-react/src/index.js | 3 - packages/ffe-grid-react/src/index.ts | 3 + packages/ffe-grid-react/src/types.ts | 52 ++++++ packages/ffe-grid-react/tsconfig.cjs.json | 9 + packages/ffe-grid-react/tsconfig.esm.json | 9 + packages/ffe-grid-react/tsconfig.types.json | 10 ++ 22 files changed, 583 insertions(+), 727 deletions(-) delete mode 100644 packages/ffe-grid-react/src/Grid.js delete mode 100644 packages/ffe-grid-react/src/Grid.spec.js create mode 100644 packages/ffe-grid-react/src/Grid.spec.tsx create mode 100644 packages/ffe-grid-react/src/Grid.tsx delete mode 100644 packages/ffe-grid-react/src/GridCol.js delete mode 100644 packages/ffe-grid-react/src/GridCol.spec.js create mode 100644 packages/ffe-grid-react/src/GridCol.spec.tsx create mode 100644 packages/ffe-grid-react/src/GridCol.tsx delete mode 100644 packages/ffe-grid-react/src/GridRow.js delete mode 100644 packages/ffe-grid-react/src/GridRow.spec.js create mode 100644 packages/ffe-grid-react/src/GridRow.spec.tsx create mode 100644 packages/ffe-grid-react/src/GridRow.tsx delete mode 100644 packages/ffe-grid-react/src/background-colors.js delete mode 100644 packages/ffe-grid-react/src/index.d.ts delete mode 100644 packages/ffe-grid-react/src/index.js create mode 100644 packages/ffe-grid-react/src/index.ts create mode 100644 packages/ffe-grid-react/src/types.ts create mode 100644 packages/ffe-grid-react/tsconfig.cjs.json create mode 100644 packages/ffe-grid-react/tsconfig.esm.json create mode 100644 packages/ffe-grid-react/tsconfig.types.json diff --git a/packages/ffe-grid-react/README.md b/packages/ffe-grid-react/README.md index 649c7a37de..a5c4a078bd 100644 --- a/packages/ffe-grid-react/README.md +++ b/packages/ffe-grid-react/README.md @@ -10,11 +10,6 @@ npm install --save @sb1/ffe-grid-react Full documentation on grid usage is available at https://design.sparebank1.no/komponenter/grid/. -## TypeScript definition files - -This component supports TypeScript - please update `index.d.ts` if you change any -of the external methods or properties in this component. - ## DevTool: Grid overlay For å lettere inspisere hvordan innholdet legger seg i kolonner, eller for å se hvordan gridden vil kunne passe inn på en eksisterende webside eksporteres det et separat util som vil lage en full-skjerm overlay med kolonner på en eksisterende side. Fra denne modulen eksporteres bare en funksjon som lager en fixed positioned div og det er opp til applikasjonen hvordan man ønsker å bruke den. diff --git a/packages/ffe-grid-react/package.json b/packages/ffe-grid-react/package.json index 93e3b3513a..407aa076f6 100644 --- a/packages/ffe-grid-react/package.json +++ b/packages/ffe-grid-react/package.json @@ -17,7 +17,7 @@ "url": "ssh://git@github.com:SpareBank1/designsystem.git" }, "scripts": { - "build": "ffe-buildtool babel", + "build": "ffe-buildtool tsc", "watch": "ffe-buildtool babel-watch", "lint": "eslint src", "lint:fix": "eslint src --fix", diff --git a/packages/ffe-grid-react/src/Grid.js b/packages/ffe-grid-react/src/Grid.js deleted file mode 100644 index 35e033dc2a..0000000000 --- a/packages/ffe-grid-react/src/Grid.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { node, string, oneOf } from 'prop-types'; - -export default function Grid({ children, className, gap, element, ...rest }) { - const Element = element || 'div'; - - return ( - - {children} - - ); -} - -Grid.propTypes = { - /** Any children of a Grid must be a GridRow */ - children: node, - /** Any extra classes are attached to the root node, in addition to ffe-grid classes */ - className: string, - /** Specify the internal gutter of the grid */ - gap: oneOf(['none', '2xs', 'xs', 'sm', 'md', 'lg']), - /** Specify the DOM element being used to create the Grid */ - element: string, -}; diff --git a/packages/ffe-grid-react/src/Grid.spec.js b/packages/ffe-grid-react/src/Grid.spec.js deleted file mode 100644 index ed3213ae59..0000000000 --- a/packages/ffe-grid-react/src/Grid.spec.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { Grid } from '.'; - -const defaultProps = { - children:

blah

, -}; - -const renderShallow = (props = {}) => - shallow(); - -describe('Grid', () => { - it('renders with default class and element', () => { - const el = renderShallow(); - - expect(el.prop('className')).toBe('ffe-grid'); - expect(el.type()).toBe('div'); - }); - - it('renders with custom class', () => { - const el = renderShallow({ className: 'custom-class' }); - expect(el.hasClass('custom-class')).toBe(true); - }); - - it('renders provided children node', () => { - const el = renderShallow(); - - expect(el.containsMatchingElement(

blah

)).toBe(true); - }); - - it('sets the gap modifier', () => { - const el = renderShallow({ gap: 'xs' }); - - expect(el.hasClass('ffe-grid')).toBe(true); - expect(el.hasClass('ffe-grid--gap-xs')).toBe(true); - }); - - it('preserves other attributes that are passed to it', () => { - const handler = jest.fn(); - const el = renderShallow({ onClick: handler }); - - el.simulate('click'); - - expect(handler).toHaveBeenCalledTimes(1); - }); - - it('can render a custom root element', () => { - const el = renderShallow({ element: 'section' }); - - expect(el.type()).toBe('section'); - }); -}); diff --git a/packages/ffe-grid-react/src/Grid.spec.tsx b/packages/ffe-grid-react/src/Grid.spec.tsx new file mode 100644 index 0000000000..60f6ccdc1d --- /dev/null +++ b/packages/ffe-grid-react/src/Grid.spec.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Grid, GridProps } from './Grid'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +const defaultProps = { + children:

blah

, +}; + +const TEST_ID = 'test-id'; + +const renderGrid = (props?: Partial) => + render(); + +describe('', () => { + it('renders with default class and element', () => { + renderGrid(); + const grid = screen.getByTestId(TEST_ID); + expect(grid.tagName).toBe('DIV'); + expect(grid.classList.contains('ffe-grid')).toBeTruthy(); + }); + + it('renders with custom class', () => { + renderGrid({ className: 'custom-class' }); + const grid = screen.getByTestId(TEST_ID); + expect(grid.classList.contains('ffe-grid')).toBeTruthy(); + expect(grid.classList.contains('custom-class')).toBeTruthy(); + }); + + it('renders provided children node', () => { + renderGrid(); + const grid = screen.getByTestId(TEST_ID); + + expect(grid.innerHTML).toBe('

blah

'); + }); + + it('sets the gap modifier', () => { + renderGrid({ gap: 'xs' }); + + const grid = screen.getByTestId(TEST_ID); + expect(grid.classList.contains('ffe-grid')).toBeTruthy(); + expect(grid.classList.contains('ffe-grid--gap-xs')).toBeTruthy(); + }); + + it('preserves other attributes that are passed to it', async () => { + const user = userEvent.setup(); + + const handler = jest.fn(); + renderGrid({ onClick: handler }); + const grid = screen.getByTestId(TEST_ID); + await user.click(grid); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('can render a custom root element', () => { + renderGrid({ as: 'section' }); + const grid = screen.getByTestId(TEST_ID); + expect(grid.tagName).toBe('SECTION'); + }); +}); diff --git a/packages/ffe-grid-react/src/Grid.tsx b/packages/ffe-grid-react/src/Grid.tsx new file mode 100644 index 0000000000..e65e14d2db --- /dev/null +++ b/packages/ffe-grid-react/src/Grid.tsx @@ -0,0 +1,28 @@ +import React, { ElementType } from 'react'; +import classNames from 'classnames'; +import { ComponentWithoutRefAsPropParams } from './types'; + +export type GridProps = + ComponentWithoutRefAsPropParams & { + /** Specify the internal gutter of the grid */ + gap?: 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg'; + }; + +export const Grid: React.FC = ({ + children, + className, + gap, + as: Comp = 'div', + ...rest +}) => { + return ( + + {children} + + ); +}; diff --git a/packages/ffe-grid-react/src/GridCol.js b/packages/ffe-grid-react/src/GridCol.js deleted file mode 100644 index b652142820..0000000000 --- a/packages/ffe-grid-react/src/GridCol.js +++ /dev/null @@ -1,158 +0,0 @@ -import React, { Component, createRef } from 'react'; -import { - bool, - node, - number, - oneOf, - oneOfType, - shape, - string, -} from 'prop-types'; -import classNames from 'classnames'; -import { - backgroundColors, - backgroundDarkColors, - removedColors, -} from './background-colors'; - -const sizeClasses = (size, def) => { - switch (typeof def) { - case 'undefined': - return ''; - case 'object': - return classNames({ - [`ffe-grid__col--${size}-${def.cols}`]: - def.cols || def.cols === 0, - [`ffe-grid__col--${size}-offset-${def.offset}`]: - def.offset || def.offset === 0, - }); - default: - return `ffe-grid__col--${size}-${def}`; - } -}; - -const centerTextClass = centerText => - centerText ? 'ffe-grid__col--center-text' : null; - -const centerClass = center => (center ? 'ffe-grid__col--center' : null); - -const backgroundClass = background => { - if (!background) { - return null; - } - - if (removedColors.includes(background)) { - throw new Error( - `Support for the ${background} background on has been removed, please see the CHANGELOG`, - ); - } - - return backgroundColors.includes(background) - ? `ffe-grid__col--bg-${background}` - : null; -}; - -const backgroundDarkClass = backgroundDark => - backgroundDarkColors.includes(backgroundDark) - ? `ffe-grid__col--bg-dark-${backgroundDark}` - : null; - -export default class GridCol extends Component { - constructor() { - super(); - - this.ref = createRef(); - } - - render() { - const { - background, - backgroundDark, - className, - element: Element = 'div', - centerText, - center, - children, - sm, - md, - lg, - ...rest - } = this.props; - - const classes = [ - 'ffe-grid__col', - className, - sizeClasses('lg', lg), - sizeClasses('md', md), - sizeClasses('sm', !sm && !lg && !md ? 12 : sm), - centerTextClass(centerText), - centerClass(center), - backgroundClass(background), - backgroundDarkClass(backgroundDark), - ] - .filter(x => x) - .join(' '); - - return ( - - {children} - - ); - } -} - -GridCol.propTypes = { - /** Supported background colors */ - background: oneOf(backgroundColors), - /** Supported dark background colors */ - backgroundDark: oneOf(backgroundDarkColors), - /** Any extra classes are attached to the root node, in addition to ffe-grid__col classes */ - className: string, - /** Specify the DOM element being used to create the GridCol */ - element: node, - /** Center text content horizontally */ - centerText: bool, - /** Center content vertically */ - center: bool, - /** The content of the column */ - children: node, - /** Size modifiers for small screen sizes */ - sm: oneOfType([ - /** Number of columns, 0-12 */ - number, - /** Number of columns, 0-12 */ - string, - shape({ - /** Number of columns, 0-12 */ - cols: oneOfType([number, string]), - /** Number of colums offset, 0-12 */ - offset: oneOfType([number, string]), - }), - ]), - /** Size modifiers for medium screen sizes */ - md: oneOfType([ - /** Number of columns, 0-12 */ - number, - /** Number of columns, 0-12 */ - string, - shape({ - /** Number of columns, 0-12 */ - cols: oneOfType([number, string]), - /** Number of colums offset, 0-12 */ - offset: oneOfType([number, string]), - }), - ]), - /** Size modifiers for large screen sizes */ - lg: oneOfType([ - /** Number of columns, 0-12 */ - number, - /** Number of columns, 0-12 */ - string, - shape({ - /** Number of columns, 0-12 */ - cols: oneOfType([number, string]), - /** Number of colums offset, 0-12 */ - offset: oneOfType([number, string]), - }), - ]), -}; diff --git a/packages/ffe-grid-react/src/GridCol.spec.js b/packages/ffe-grid-react/src/GridCol.spec.js deleted file mode 100644 index 72e33221bb..0000000000 --- a/packages/ffe-grid-react/src/GridCol.spec.js +++ /dev/null @@ -1,154 +0,0 @@ -import React from 'react'; -import { GridCol } from '.'; -import { backgroundColors, backgroundDarkColors } from './background-colors'; - -const defaultProps = { - children:

blah

, -}; - -const renderShallow = (props = {}) => - shallow(); - -describe('GridCol', () => { - it('renders with custom class', () => { - const el = renderShallow({ className: 'custom-class' }); - expect(el.hasClass('custom-class')).toBe(true); - }); - - it('renders provided children node', () => { - const el = renderShallow(); - - expect(el.containsMatchingElement(

blah

)).toBe(true); - }); - - it('supports setting cols as a number', () => { - const el = renderShallow({ lg: 10 }); - - expect(el.hasClass('ffe-grid__col--lg-10')).toBe(true); - }); - - it('supports setting cols as a string', () => { - const el = renderShallow({ sm: '1' }); - - expect(el.hasClass('ffe-grid__col--sm-1')).toBe(true); - }); - - it('supports setting cols as an object', () => { - const el = renderShallow({ md: { cols: 4 } }); - - expect(el.hasClass('ffe-grid__col--md-4')).toBe(true); - }); - - it('supports setting cols and offset', () => { - const el = renderShallow({ sm: { cols: 4, offset: '2' } }); - - expect(el.hasClass('ffe-grid__col--sm-4')).toBe(true); - expect(el.hasClass('ffe-grid__col--sm-offset-2')).toBe(true); - }); - - it('does not set an offset class if no offset is provided', () => { - const el = renderShallow({ lg: { cols: 7 } }); - - expect(el.prop('className')).not.toContain('offset'); - }); - - it('sets the center modifier if the value is boolean true', () => { - const el = renderShallow({ centerText: true }); - - expect(el.hasClass('ffe-grid__col--center-text')).toBe(true); - }); - - it('sets background color class if valid', () => { - backgroundColors.forEach(background => { - const el = renderShallow({ background }); - expect(el.hasClass(`ffe-grid__col--bg-${background}`)).toBe(true); - }); - }); - - it('sets dark background color class if valid', () => { - backgroundDarkColors.forEach(backgroundDark => { - const el = renderShallow({ backgroundDark }); - expect( - el.hasClass(`ffe-grid__col--bg-dark-${backgroundDark}`), - ).toBe(true); - }); - }); - - it('does not set background color class if not valid', () => { - const illegalBackgroundColors = ['ice-cream', 'log("hack")', '123456']; - illegalBackgroundColors.forEach(background => { - const el = renderShallow({ background }); - expect(el.hasClass(`ffe-grid__col--bg-${background}`)).toBe(false); - }); - }); - - it('does not set dark background color class if not valid', () => { - const illegalBackgroundDarkColors = [ - 'ice-cream', - 'log("hack")', - '123456', - ]; - illegalBackgroundDarkColors.forEach(backgroundDark => { - const el = renderShallow({ backgroundDark }); - expect( - el.hasClass(`ffe-grid__col--bg-dark-${backgroundDark}`), - ).toBe(false); - }); - }); - - it('throws for removed background colours', () => { - expect(() => renderShallow({ background: 'blue-cobalt' })).toThrow( - 'Support for the blue-cobalt background on has been removed, please see the CHANGELOG', - ); - }); - - it('sets all the things o/', () => { - const el = renderShallow({ - sm: { cols: '6', offset: 2 }, - md: 8, - lg: { cols: 2 }, - centerText: true, - }); - - expect(el.hasClass('ffe-grid__col--sm-6')).toBe(true); - expect(el.hasClass('ffe-grid__col--sm-offset-2')).toBe(true); - expect(el.hasClass('ffe-grid__col--md-8')).toBe(true); - expect(el.hasClass('ffe-grid__col--lg-2')).toBe(true); - expect(el.hasClass('ffe-grid__col--center-text')).toBe(true); - }); - - it('preserves other attributes that are passed to it', () => { - const handler = jest.fn(); - const el = renderShallow({ onClick: handler }); - - el.simulate('click'); - - expect(handler).toHaveBeenCalledTimes(1); - }); - - it('can render a custom root element', () => { - const el = renderShallow({ element: 'section' }); - - expect(el.type()).toBe('section'); - }); - - it('can render with offset 0 and columns 0', () => { - const el = renderShallow({ md: { cols: 0, offset: 0 } }); - - expect(el.hasClass('ffe-grid__col--md-offset-0')).toBe(true); - expect(el.hasClass('ffe-grid__col--md-0')).toBe(true); - }); - - it('can render with offset 1 and columns 1', () => { - const el = renderShallow({ md: { cols: 1, offset: 1 } }); - - expect(el.hasClass('ffe-grid__col--md-offset-1')).toBe(true); - expect(el.hasClass('ffe-grid__col--md-1')).toBe(true); - }); - - it('defaults to sm={12} if no dimensions are provided', () => { - const el = renderShallow(); - - expect(el.hasClass('ffe-grid__col--sm-12')).toBe(true); - }); -}); diff --git a/packages/ffe-grid-react/src/GridCol.spec.tsx b/packages/ffe-grid-react/src/GridCol.spec.tsx new file mode 100644 index 0000000000..56eb777e13 --- /dev/null +++ b/packages/ffe-grid-react/src/GridCol.spec.tsx @@ -0,0 +1,150 @@ +import React from 'react'; +import { GridCol, GridColProps } from './GridCol'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +const TEST_ID = 'test-id'; + +const defaultProps = { + children:

blah

, +}; + +const renderGridCol = (props?: Partial) => + render(); + +describe('', () => { + it('renders with custom class', () => { + renderGridCol({ className: 'custom-class' }); + const gridCol = screen.getByTestId(TEST_ID); + + expect(gridCol.classList.contains('custom-class')).toBeTruthy(); + }); + + it('renders provided children node', () => { + renderGridCol(); + const gridCol = screen.getByTestId(TEST_ID); + + expect(gridCol.innerHTML).toBe('

blah

'); + }); + + it('supports setting cols as a number', () => { + renderGridCol({ lg: 10 }); + const gridCol = screen.getByTestId(TEST_ID); + expect(gridCol.classList.contains('ffe-grid__col--lg-10')).toBeTruthy(); + }); + + it('supports setting cols as a string', () => { + renderGridCol({ sm: '1' }); + const gridCol = screen.getByTestId(TEST_ID); + expect(gridCol.classList.contains('ffe-grid__col--sm-1')).toBeTruthy(); + }); + + it('supports setting cols as an object', () => { + renderGridCol({ md: { cols: 4 } }); + const gridCol = screen.getByTestId(TEST_ID); + expect(gridCol.classList.contains('ffe-grid__col--md-4')).toBeTruthy(); + }); + + it('supports setting cols and offset', () => { + renderGridCol({ sm: { cols: 4, offset: '2' } }); + const gridCol = screen.getByTestId(TEST_ID); + expect(gridCol.classList.contains('ffe-grid__col--sm-4')).toBeTruthy(); + expect( + gridCol.classList.contains('ffe-grid__col--sm-offset-2'), + ).toBeTruthy(); + }); + + it('does not set an offset class if no offset is provided', () => { + renderGridCol({ lg: { cols: 7 } }); + const gridCol = screen.getByTestId(TEST_ID); + expect(gridCol.getAttribute('class')).not.toContain('offset'); + }); + + it('sets the center modifier if the value is boolean true', () => { + renderGridCol({ centerText: true }); + const gridCol = screen.getByTestId(TEST_ID); + expect( + gridCol.classList.contains('ffe-grid__col--center-text'), + ).toBeTruthy(); + }); + + it('sets background color class if valid', () => { + renderGridCol({ background: 'frost-30' }); + const gridCol = screen.getByTestId(TEST_ID); + expect( + gridCol.classList.contains(`ffe-grid__col--bg-frost-30`), + ).toBeTruthy(); + }); + + it('sets background color class if valid', () => { + renderGridCol({ backgroundDark: 'koksgraa' }); + const gridCol = screen.getByTestId(TEST_ID); + expect( + gridCol.classList.contains(`ffe-grid__col--bg-dark-koksgraa`), + ).toBeTruthy(); + }); + + it('sets all the things o/', () => { + renderGridCol({ + sm: { cols: '6', offset: 2 }, + md: 8, + lg: { cols: 2 }, + centerText: true, + }); + const gridCol = screen.getByTestId(TEST_ID); + expect(gridCol.classList.contains('ffe-grid__col--sm-6')).toBeTruthy(); + expect( + gridCol.classList.contains('ffe-grid__col--sm-offset-2'), + ).toBeTruthy(); + expect(gridCol.classList.contains('ffe-grid__col--md-8')).toBeTruthy(); + expect(gridCol.classList.contains('ffe-grid__col--lg-2')).toBeTruthy(); + expect( + gridCol.classList.contains('ffe-grid__col--center-text'), + ).toBeTruthy(); + }); + + it('preserves other attributes that are passed to it', async () => { + const user = userEvent.setup(); + + const handler = jest.fn(); + renderGridCol({ onClick: handler }); + const gridCol = screen.getByTestId(TEST_ID); + + await user.click(gridCol); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('can render a custom root element', () => { + renderGridCol({ as: 'section' }); + const gridCol = screen.getByTestId(TEST_ID); + expect(gridCol.tagName).toBe('SECTION'); + }); + + it('can render with offset 0 and columns 0', () => { + renderGridCol({ md: { cols: 0, offset: 0 } }); + const gridCol = screen.getByTestId(TEST_ID); + + expect( + gridCol.classList.contains('ffe-grid__col--md-offset-0'), + ).toBeTruthy(); + expect(gridCol.classList.contains('ffe-grid__col--md-0')).toBeTruthy(); + }); + + it('can render with offset 1 and columns 1', () => { + renderGridCol({ md: { cols: 1, offset: 1 } }); + const gridCol = screen.getByTestId(TEST_ID); + + expect( + gridCol.classList.contains('ffe-grid__col--md-offset-1'), + ).toBeTruthy(); + expect(gridCol.classList.contains('ffe-grid__col--md-1')).toBeTruthy(); + }); + + it('defaults to sm={12} if no dimensions are provided', () => { + renderGridCol(); + const gridCol = screen.getByTestId(TEST_ID); + + expect(gridCol.classList.contains('ffe-grid__col--sm-12')).toBeTruthy(); + }); +}); diff --git a/packages/ffe-grid-react/src/GridCol.tsx b/packages/ffe-grid-react/src/GridCol.tsx new file mode 100644 index 0000000000..4e1b623684 --- /dev/null +++ b/packages/ffe-grid-react/src/GridCol.tsx @@ -0,0 +1,121 @@ +import React, { ElementType } from 'react'; +import classNames from 'classnames'; +import { + BackgroundColor, + BackgroundColorDark, + ComponentWithoutRefAsPropParams, +} from './types'; + +type ColumnsRange = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | '0' + | '1' + | '2' + | '3' + | '4' + | '5' + | '6' + | '7' + | '8' + | '9' + | '10' + | '11' + | '12'; + +interface GridColSize { + cols: ColumnsRange; + offset?: ColumnsRange; +} + +type SizeModifier = ColumnsRange | GridColSize; + +export type GridColProps = + ComponentWithoutRefAsPropParams & { + /** Supported background colors */ + background?: BackgroundColor; + /** Supported dark background colors */ + backgroundDark?: BackgroundColorDark; + /** Center text content horizontally */ + centerText?: boolean; + /** Center content vertically */ + center?: boolean; + /** Size modifiers for small screen sizes */ + sm?: SizeModifier; + /** Size modifiers for medium screen sizes */ + md?: SizeModifier; + /** Size modifiers for large screen sizes */ + lg?: SizeModifier; + }; + +const sizeClasses = (size: 'sm' | 'md' | 'lg', def?: SizeModifier) => { + if (def === undefined) { + return null; + } else if (typeof def === 'number' || typeof def === 'string') { + return `ffe-grid__col--${size}-${def}`; + } + + return classNames({ + [`ffe-grid__col--${size}-${def.cols}`]: def.cols || def.cols === 0, + [`ffe-grid__col--${size}-offset-${def.offset}`]: + def.offset || def.offset === 0, + }); +}; + +const centerTextClass = (centerText?: boolean) => + centerText ? 'ffe-grid__col--center-text' : null; + +const centerClass = (center?: boolean) => + center ? 'ffe-grid__col--center' : null; + +const backgroundClass = (background?: BackgroundColor) => + background ? `ffe-grid__col--bg-${background}` : null; + +const backgroundDarkClass = (backgroundDark?: BackgroundColorDark) => + backgroundDark ? `ffe-grid__col--bg-dark-${backgroundDark}` : null; + +export const GridCol: React.FC = ({ + background, + backgroundDark, + className, + as: Comp = 'div', + centerText, + center, + children, + sm, + md, + lg, + ...rest +}) => { + return ( + x) + .join(' ')} + {...rest} + > + {children} + + ); +}; diff --git a/packages/ffe-grid-react/src/GridRow.js b/packages/ffe-grid-react/src/GridRow.js deleted file mode 100644 index b362909672..0000000000 --- a/packages/ffe-grid-react/src/GridRow.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { node, oneOf, string } from 'prop-types'; -import classNames from 'classnames'; - -import { - backgroundColors, - backgroundDarkColors, - removedColors, -} from './background-colors'; - -export default function GridRow({ - background, - backgroundDark, - className, - children, - element, - padding, - margin, - ...rest -}) { - const hasBackgroundColor = backgroundColors.includes(background); - const hasBackgroundDarkColor = - backgroundDarkColors.includes(backgroundDark); - const hasRemovedColor = removedColors.includes(background); - const content = - hasBackgroundColor || hasBackgroundDarkColor ? ( -
{children}
- ) : ( - children - ); - - if (hasRemovedColor) { - throw new Error( - `Support for the ${background} background on has been removed, please see the CHANGELOG`, - ); - } - - const Element = element || 'div'; - - return ( - - {content} - - ); -} - -GridRow.propTypes = { - /** Supported background colors */ - background: oneOf(backgroundColors), - /** Supported dark background colors */ - backgroundDark: oneOf(backgroundDarkColors), - /** Padding in the top and bottom of the row */ - padding: oneOf([ - '2xs', - 'xs', - 'sm', - 'md', - 'lg', - 'xl', - '2xl', - '3xl', - '4xl', - '5xl', - ]), - /** Margin in the top and bottom of the row */ - margin: oneOf([ - '2xs', - 'xs', - 'sm', - 'md', - 'lg', - 'xl', - '2xl', - '3xl', - '4xl', - '5xl', - ]), - /** Any extra classes are attached to the root node, in addition to ffe-grid__row classes */ - className: string, - /** All children of a `` must be ``. */ - children: node, - /** Specify the DOM element being used to create the GridRow */ - element: string, -}; diff --git a/packages/ffe-grid-react/src/GridRow.spec.js b/packages/ffe-grid-react/src/GridRow.spec.js deleted file mode 100644 index c6a9a6b838..0000000000 --- a/packages/ffe-grid-react/src/GridRow.spec.js +++ /dev/null @@ -1,118 +0,0 @@ -import React from 'react'; - -import { GridRow, GridCol } from '.'; -import { backgroundColors, backgroundDarkColors } from './background-colors'; - -const defaultProps = { - children:

blah

, -}; - -const renderShallow = (props = {}) => - shallow(); - -describe('GridRow', () => { - it('renders with default class and element', () => { - const el = renderShallow(); - - expect(el.prop('className')).toBe('ffe-grid__row'); - expect(el.type()).toBe('div'); - }); - - it('renders with custom class', () => { - const el = renderShallow({ className: 'custom-class' }); - expect(el.hasClass('custom-class')).toBe(true); - }); - - it('renders provided children node', () => { - const el = renderShallow(); - - expect(el.containsMatchingElement(

blah

)).toBe(true); - }); - - it('adds correct class for all valid background colors', () => { - const el = renderShallow(); - backgroundColors.forEach(background => { - el.setProps({ background }); - expect(el.hasClass(`ffe-grid__row--bg-${background}`)).toBe(true); - }); - }); - - it('adds correct class for all valid dark background colors', () => { - const el = renderShallow(); - backgroundDarkColors.forEach(backgroundDark => { - el.setProps({ backgroundDark }); - expect( - el.hasClass(`ffe-grid__row--bg-dark-${backgroundDark}`), - ).toBe(true); - }); - }); - - it('does not add any background-class for invalid background colors', () => { - const el = renderShallow(); - console.error = jest.fn(); - ['illegal', 'color values', 'are ignored'].forEach(background => { - el.setProps({ background }); - expect(el.hasClass(`ffe-grid__row--bg-${background}`)).toBe(false); - }); - }); - - it('does not add any dark background-class for invalid background colors', () => { - const el = renderShallow(); - console.error = jest.fn(); - ['illegal', 'color values', 'are ignored'].forEach(backgroundDark => { - el.setProps({ backgroundDark }); - expect( - el.hasClass(`ffe-grid__row--bg-dark-${backgroundDark}`), - ).toBe(false); - }); - }); - - it('throws for removed background colours', () => { - expect(() => renderShallow({ background: 'blue-cobalt' })).toThrow( - 'Support for the blue-cobalt background on has been removed, please see the CHANGELOG', - ); - }); - - it('renders rows with extra wrappers when background is set', () => { - backgroundColors.forEach(background => { - const el = renderShallow({ - background, - children: ( - -

blah

-
- ), - }); - expect(el.childAt(0).hasClass('ffe-grid__row-wrapper')).toBe(true); - }); - }); - - it('renders rows with extra wrappers when dark background is set', () => { - backgroundDarkColors.forEach(backgroundDark => { - const el = renderShallow({ - backgroundDark, - children: ( - -

blah

-
- ), - }); - expect(el.childAt(0).hasClass('ffe-grid__row-wrapper')).toBe(true); - }); - }); - - it('preserves other attributes that are passed to it', () => { - const handler = jest.fn(); - const el = renderShallow({ onClick: handler }); - - el.simulate('click'); - - expect(handler).toHaveBeenCalledTimes(1); - }); - - it('can render a custom root element', () => { - const el = renderShallow({ element: 'section' }); - - expect(el.type()).toBe('section'); - }); -}); diff --git a/packages/ffe-grid-react/src/GridRow.spec.tsx b/packages/ffe-grid-react/src/GridRow.spec.tsx new file mode 100644 index 0000000000..91406531d5 --- /dev/null +++ b/packages/ffe-grid-react/src/GridRow.spec.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { GridRow, GridRowProps } from './GridRow'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +const TEST_ID = 'test-id'; + +const defaultProps = { + children:

blah

, +}; + +const renderGridRow = (props?: Partial) => + render(); + +describe('', () => { + it('renders with default class and element', () => { + renderGridRow(); + const gridRow = screen.getByTestId(TEST_ID); + expect(gridRow.classList.contains('ffe-grid__row')).toBeTruthy(); + expect(gridRow.tagName).toBe('DIV'); + }); + + it('renders with custom class', () => { + renderGridRow({ className: 'custom-class' }); + const gridRow = screen.getByTestId(TEST_ID); + expect(gridRow.classList.contains('custom-class')).toBeTruthy(); + }); + + it('renders provided children node', () => { + renderGridRow(); + const gridRow = screen.getByTestId(TEST_ID); + + expect(gridRow.innerHTML).toBe('

blah

'); + }); + + it('adds correct class for all valid background colors', () => { + renderGridRow({ background: 'sand' }); + const gridRow = screen.getByTestId(TEST_ID); + + expect(gridRow.classList.contains(`ffe-grid__row--bg-sand`)).toBe(true); + }); + + it('adds correct class for all valid dark background colors', () => { + renderGridRow({ backgroundDark: 'natt' }); + const gridRow = screen.getByTestId(TEST_ID); + expect(gridRow.classList.contains(`ffe-grid__row--bg-dark-natt`)).toBe( + true, + ); + }); + + it('renders rows with extra wrappers when background is set', () => { + renderGridRow({ background: 'sand' }); + const gridRow = screen.getByTestId(TEST_ID); + expect(gridRow.querySelector('.ffe-grid__row-wrapper')).toBeTruthy(); + }); + + it('renders rows with extra wrappers when dark background is set', () => { + renderGridRow({ backgroundDark: 'koksgraa' }); + const gridRow = screen.getByTestId(TEST_ID); + expect(gridRow.querySelector('.ffe-grid__row-wrapper')).toBeTruthy(); + }); + + it('preserves other attributes that are passed to it', async () => { + const user = userEvent.setup(); + const handler = jest.fn(); + renderGridRow({ onClick: handler }); + const gridRow = screen.getByTestId(TEST_ID); + await user.click(gridRow); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('can render a custom root element', () => { + renderGridRow({ as: 'section' }); + const gridRow = screen.getByTestId(TEST_ID); + + expect(gridRow.tagName).toBe('SECTION'); + }); +}); diff --git a/packages/ffe-grid-react/src/GridRow.tsx b/packages/ffe-grid-react/src/GridRow.tsx new file mode 100644 index 0000000000..aba98a13a5 --- /dev/null +++ b/packages/ffe-grid-react/src/GridRow.tsx @@ -0,0 +1,60 @@ +import React, { ElementType } from 'react'; +import classNames from 'classnames'; +import { + ComponentWithoutRefAsPropParams, + BackgroundColor, + BackgroundColorDark, + Margin, + Padding, +} from './types'; + +export type GridRowProps = + ComponentWithoutRefAsPropParams & { + /** Padding in the top and bottom of the row */ + padding?: Padding; + /** Margin in the top and bottom of the row */ + margin?: Margin; + /** Supported background colors */ + background?: BackgroundColor; + /** Supported dark background colors */ + backgroundDark?: BackgroundColorDark; + }; + +export const GridRow: React.FC = ({ + background, + backgroundDark, + className, + children, + as: Comp = 'div', + padding, + margin, + ...rest +}) => { + const hasBackgroundColor = !!background; + const hasBackgroundDarkColor = !!backgroundDark; + const content = + hasBackgroundColor || hasBackgroundDarkColor ? ( +
{children}
+ ) : ( + children + ); + + return ( + + {content} + + ); +}; diff --git a/packages/ffe-grid-react/src/background-colors.js b/packages/ffe-grid-react/src/background-colors.js deleted file mode 100644 index 6ee26094ec..0000000000 --- a/packages/ffe-grid-react/src/background-colors.js +++ /dev/null @@ -1,29 +0,0 @@ -export const backgroundColors = [ - 'frost-30', - 'sand', - 'sand-70', - 'sand-30', - 'syrin-70', - 'syrin-30', - 'vann', - 'vann-30', - 'fjell', - 'hvit', -]; - -export const backgroundDarkColors = ['svart', 'natt', 'koksgraa']; - -export const removedColors = [ - 'blue-cobalt', - 'blue-royal', - 'purple-magenta', - 'blue-ice', - 'blue-pale', - 'green-mint', - 'grey-cloud', - 'grey-warm', - 'orange-salmon', - 'red', - 'blue-sky', - 'white', -]; diff --git a/packages/ffe-grid-react/src/index.d.ts b/packages/ffe-grid-react/src/index.d.ts deleted file mode 100644 index f7776f508b..0000000000 --- a/packages/ffe-grid-react/src/index.d.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as React from 'react'; - -type Gap = 'none' | '2xs' | 'xs' | 'md' | 'lg'; - -export interface GridProps extends React.ComponentProps<'div'> { - children: React.ReactNode; - className?: string; - element?: string; - gap?: Gap; -} - -type BackgroundColors = - | 'frost-30' - | 'sand' - | 'sand-70' - | 'sand-30' - | 'syrin-70' - | 'syrin-30' - | 'vann' - | 'vann-30' - | 'fjell' - | 'hvit'; - -type BackgroundDarkColors = 'svart' | 'natt' | 'koksgraa'; - -type Margin = - | '2xs' - | 'xs' - | 'sm' - | 'md' - | 'lg' - | 'xl' - | '2xl' - | '3xl' - | '4xl' - | '5xl'; - -type Padding = - | '2xs' - | 'xs' - | 'sm' - | 'md' - | 'lg' - | 'xl' - | '2xl' - | '3xl' - | '4xl' - | '5xl'; - -export interface GridRowProps extends React.ComponentProps<'div'> { - background?: BackgroundColors; - backgroundDark?: BackgroundDarkColors; - children: React.ReactNode; - className?: string; - element?: string; - padding?: Padding; - margin?: Margin; -} - -type ColumnsRange = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; - -export interface GridColSize { - cols: ColumnsRange | string; - offset: ColumnsRange | string; -} - -export interface GridColProps extends React.ComponentProps<'div'> { - background?: BackgroundColors; - backgroundDark?: BackgroundDarkColors; - children?: React.ReactNode; - className?: string; - element?: React.ReactNode; - center?: boolean; - centerText?: boolean; - sm?: ColumnsRange | string | GridColSize; - md?: ColumnsRange | string | GridColSize; - lg?: ColumnsRange | string | GridColSize; -} - -declare class Grid extends React.Component {} -declare class GridRow extends React.Component {} -declare class GridCol extends React.Component {} diff --git a/packages/ffe-grid-react/src/index.js b/packages/ffe-grid-react/src/index.js deleted file mode 100644 index c17670f85f..0000000000 --- a/packages/ffe-grid-react/src/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { default as Grid } from './Grid'; -export { default as GridRow } from './GridRow'; -export { default as GridCol } from './GridCol'; diff --git a/packages/ffe-grid-react/src/index.ts b/packages/ffe-grid-react/src/index.ts new file mode 100644 index 0000000000..dcfc27b3f8 --- /dev/null +++ b/packages/ffe-grid-react/src/index.ts @@ -0,0 +1,3 @@ +export { Grid, GridProps } from './Grid'; +export { GridRow, GridRowProps } from './GridRow'; +export { GridCol, GridColProps } from './GridCol'; diff --git a/packages/ffe-grid-react/src/types.ts b/packages/ffe-grid-react/src/types.ts new file mode 100644 index 0000000000..a838f87b75 --- /dev/null +++ b/packages/ffe-grid-react/src/types.ts @@ -0,0 +1,52 @@ +import { ComponentPropsWithoutRef, ElementType } from 'react'; + +export type DistributiveOmit = T extends any + ? Omit + : never; + +export type ComponentWithoutRefAsPropParams = { + as?: As; +} & DistributiveOmit< + ComponentPropsWithoutRef, + 'as' +>; + +export type Optional = Pick, K> & Omit; + +export type Margin = + | '2xs' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | '2xl' + | '3xl' + | '4xl' + | '5xl'; + +export type Padding = + | '2xs' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | '2xl' + | '3xl' + | '4xl' + | '5xl'; + +export type BackgroundColor = + | 'frost-30' + | 'sand' + | 'sand-70' + | 'sand-30' + | 'syrin-70' + | 'syrin-30' + | 'vann' + | 'vann-30' + | 'fjell' + | 'hvit'; + +export type BackgroundColorDark = 'svart' | 'natt' | 'koksgraa'; diff --git a/packages/ffe-grid-react/tsconfig.cjs.json b/packages/ffe-grid-react/tsconfig.cjs.json new file mode 100644 index 0000000000..6579fd2246 --- /dev/null +++ b/packages/ffe-grid-react/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib", + "module": "commonjs" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "src/**/*.spec.ts*"] +} diff --git a/packages/ffe-grid-react/tsconfig.esm.json b/packages/ffe-grid-react/tsconfig.esm.json new file mode 100644 index 0000000000..8e577796bf --- /dev/null +++ b/packages/ffe-grid-react/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./es", + "module": "esnext" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "src/**/*.spec.ts*"] +} diff --git a/packages/ffe-grid-react/tsconfig.types.json b/packages/ffe-grid-react/tsconfig.types.json new file mode 100644 index 0000000000..3499c0be03 --- /dev/null +++ b/packages/ffe-grid-react/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./types", + "declaration": true, + "emitDeclarationOnly": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "src/**/*.spec.ts*"] +}