diff --git a/package-lock.json b/package-lock.json index 9982ddc9199..f7d3d8ca1cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5576,6 +5576,52 @@ "node": ">=10" } }, + "node_modules/@leafygreen-ui/radio-group": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/radio-group/-/radio-group-7.0.6.tgz", + "integrity": "sha512-LsokgU+fX2b9teMeiE0VPNH6K9RsGOwesmiZr40q3Df+LWVTMqsjWqIMhXWe1gAwgqqHewdkaXb4eLCLG3rs2w==", + "dependencies": { + "@leafygreen-ui/hooks": "^7.0.0", + "@leafygreen-ui/interaction-ring": "^1.1.0", + "@leafygreen-ui/lib": "^9.0.0", + "@leafygreen-ui/palette": "^3.2.2" + } + }, + "node_modules/@leafygreen-ui/radio-group/node_modules/@leafygreen-ui/emotion": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-4.0.0.tgz", + "integrity": "sha512-nr2g6OFsy+psaMto3H4HQ1ivM1tCwd9k1bbR5WH4U7YibDagfBekFTwlhohmC/K7hUM/eDVPGw0w4zQyC+BwZg==", + "dependencies": { + "@emotion/css": "^11.1.3", + "@emotion/react": "^11.4.0", + "@emotion/server": "^11.4.0" + } + }, + "node_modules/@leafygreen-ui/radio-group/node_modules/@leafygreen-ui/lib": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-9.1.0.tgz", + "integrity": "sha512-JH3mnoCZNtUcJfXrvVG4I3PAIm1ehlR1H5WkDqkjengcf/iVMo7+AluzwfTCQb2fqQgohURs+qY4vEhJe+9+2g==", + "dependencies": { + "@leafygreen-ui/emotion": "^4.0.0", + "facepaint": "^1.2.1", + "polished": "^4.1.3", + "prop-types": "^15.0.0" + }, + "peerDependencies": { + "react": "^17.0.0" + } + }, + "node_modules/@leafygreen-ui/radio-group/node_modules/polished": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.1.3.tgz", + "integrity": "sha512-ocPAcVBUOryJEKe0z2KLd1l9EBa1r5mSwlKpExmrLzsnIzJo4axsoU9O2BjOTkDGDT4mZ0WFE5XKTlR3nLnZOA==", + "dependencies": { + "@babel/runtime": "^7.14.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@leafygreen-ui/ripple": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@leafygreen-ui/ripple/-/ripple-1.1.1.tgz", @@ -63585,6 +63631,7 @@ "@leafygreen-ui/palette": "^3.2.2", "@leafygreen-ui/portal": "^3.1.3", "@leafygreen-ui/radio-box-group": "^6.1.4", + "@leafygreen-ui/radio-group": "^7.0.6", "@leafygreen-ui/segmented-control": "^0.9.1", "@leafygreen-ui/select": "^3.0.8", "@leafygreen-ui/table": "^2.0.5", @@ -94183,17 +94230,6 @@ "decamelize": "^1.2.0" } }, - "packages/compass/node_modules/@mongosh/node-runtime-worker-thread": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongosh/node-runtime-worker-thread/-/node-runtime-worker-thread-1.1.9.tgz", - "integrity": "sha512-wbct5QHogcVsPmIz+thXe6CTw7GnG53OtPDumwY63TKsxCJjrcycCjwE2+xXommAhqgv3KkYxj1DPvBlsS8KXA==", - "dependencies": { - "interruptor": "^1.0.1" - }, - "engines": { - "node": ">=12.4.0" - } - }, "packages/compass/node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -108753,6 +108789,48 @@ } } }, + "@leafygreen-ui/radio-group": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/radio-group/-/radio-group-7.0.6.tgz", + "integrity": "sha512-LsokgU+fX2b9teMeiE0VPNH6K9RsGOwesmiZr40q3Df+LWVTMqsjWqIMhXWe1gAwgqqHewdkaXb4eLCLG3rs2w==", + "requires": { + "@leafygreen-ui/hooks": "^7.0.0", + "@leafygreen-ui/interaction-ring": "^1.1.0", + "@leafygreen-ui/lib": "^9.0.0", + "@leafygreen-ui/palette": "^3.2.2" + }, + "dependencies": { + "@leafygreen-ui/emotion": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-4.0.0.tgz", + "integrity": "sha512-nr2g6OFsy+psaMto3H4HQ1ivM1tCwd9k1bbR5WH4U7YibDagfBekFTwlhohmC/K7hUM/eDVPGw0w4zQyC+BwZg==", + "requires": { + "@emotion/css": "^11.1.3", + "@emotion/react": "^11.4.0", + "@emotion/server": "^11.4.0" + } + }, + "@leafygreen-ui/lib": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-9.1.0.tgz", + "integrity": "sha512-JH3mnoCZNtUcJfXrvVG4I3PAIm1ehlR1H5WkDqkjengcf/iVMo7+AluzwfTCQb2fqQgohURs+qY4vEhJe+9+2g==", + "requires": { + "@leafygreen-ui/emotion": "^4.0.0", + "facepaint": "^1.2.1", + "polished": "^4.1.3", + "prop-types": "^15.0.0" + } + }, + "polished": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.1.3.tgz", + "integrity": "sha512-ocPAcVBUOryJEKe0z2KLd1l9EBa1r5mSwlKpExmrLzsnIzJo4axsoU9O2BjOTkDGDT4mZ0WFE5XKTlR3nLnZOA==", + "requires": { + "@babel/runtime": "^7.14.0" + } + } + } + }, "@leafygreen-ui/ripple": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@leafygreen-ui/ripple/-/ripple-1.1.1.tgz", @@ -114689,6 +114767,7 @@ "@leafygreen-ui/palette": "^3.2.2", "@leafygreen-ui/portal": "^3.1.3", "@leafygreen-ui/radio-box-group": "^6.1.4", + "@leafygreen-ui/radio-group": "^7.0.6", "@leafygreen-ui/segmented-control": "^0.9.1", "@leafygreen-ui/select": "^3.0.8", "@leafygreen-ui/table": "^2.0.5", @@ -172632,14 +172711,6 @@ "web-vitals": "^2.1.2" }, "dependencies": { - "@mongosh/node-runtime-worker-thread": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongosh/node-runtime-worker-thread/-/node-runtime-worker-thread-1.1.9.tgz", - "integrity": "sha512-wbct5QHogcVsPmIz+thXe6CTw7GnG53OtPDumwY63TKsxCJjrcycCjwE2+xXommAhqgv3KkYxj1DPvBlsS8KXA==", - "requires": { - "interruptor": "^1.0.1" - } - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", diff --git a/packages/compass-components/package.json b/packages/compass-components/package.json index 2d829db9cb1..cf202fb38db 100644 --- a/packages/compass-components/package.json +++ b/packages/compass-components/package.json @@ -52,6 +52,7 @@ "@leafygreen-ui/palette": "^3.2.2", "@leafygreen-ui/portal": "^3.1.3", "@leafygreen-ui/radio-box-group": "^6.1.4", + "@leafygreen-ui/radio-group": "^7.0.6", "@leafygreen-ui/segmented-control": "^0.9.1", "@leafygreen-ui/select": "^3.0.8", "@leafygreen-ui/table": "^2.0.5", diff --git a/packages/compass-components/src/components/file-input.spec.tsx b/packages/compass-components/src/components/file-input.spec.tsx index f91576f650e..3dcc36bbc1b 100644 --- a/packages/compass-components/src/components/file-input.spec.tsx +++ b/packages/compass-components/src/components/file-input.spec.tsx @@ -2,7 +2,13 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { render, screen, cleanup } from '@testing-library/react'; +import { + render, + screen, + cleanup, + fireEvent, + waitFor, +} from '@testing-library/react'; import FileInput, { Variant } from './file-input'; @@ -182,4 +188,126 @@ describe('FileInput', function () { const errorMessage = screen.getByTestId('file-input-error'); expect(errorMessage).to.exist; }); + + it('does not show optional if not specified', function () { + render( + + ); + + expect(screen.queryByText('Optional')).to.equal(null); + }); + + it('renders the optional when specified', function () { + render( + + ); + + expect(screen.getByText('Optional')).to.be.visible; + }); + + it('renders the optional message when specified', function () { + render( + + ); + + expect(screen.getByText('pineapples')).to.be.visible; + }); + + describe('when a file is chosen', function () { + beforeEach(async function () { + render( + + ); + + const fileInput = screen.getByTestId('test-file-input'); + + await waitFor(() => + fireEvent.change(fileInput, { + target: { + files: [ + { + path: 'new/file/path', + }, + ], + }, + }) + ); + }); + + it('calls onChange with the chosen file', function () { + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.deep.equal(['new/file/path']); + }); + }); + + it('renders the file name with close button when showFileOnNewLine=true', function () { + render( + + ); + + expect(screen.getByText('new/file/nice')).to.be.visible; + expect(screen.getByText('another/file/path')).to.be.visible; + expect(screen.getAllByLabelText('Remove file').length).to.equal(2); + }); + + describe('when a file is clicked to remove on multi line', function () { + beforeEach(async function () { + render( + + ); + + const removeButton = screen.getAllByLabelText('Remove file')[0]; + + await waitFor(() => fireEvent.click(removeButton)); + }); + + it('calls onChange with the file removed', function () { + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.deep.equal(['another/file/path']); + }); + }); }); diff --git a/packages/compass-components/src/components/file-input.tsx b/packages/compass-components/src/components/file-input.tsx index cab5ad3509d..94180a3c006 100644 --- a/packages/compass-components/src/components/file-input.tsx +++ b/packages/compass-components/src/components/file-input.tsx @@ -4,7 +4,7 @@ import { css, cx } from '@leafygreen-ui/emotion'; import { uiColors } from '@leafygreen-ui/palette'; import { spacing } from '@leafygreen-ui/tokens'; -import { Button, Icon, Label, Link, Description } from '..'; +import { Button, Icon, IconButton, Label, Link, Description } from '..'; const { base: redBaseColor } = uiColors.red; @@ -14,16 +14,24 @@ const formItemHorizontalStyles = css({ marginRight: 'auto', marginLeft: 'auto', display: 'flex', - alignItems: 'center', }); -const formItemVerticalStyles = css` - margin: 5px auto 20px; -`; +const formItemVerticalStyles = css({ + margin: '5px auto 20px', +}); -const buttonStyles = css` - width: 100%; -`; +const removeFileLineStyles = css({ + display: 'flex', + flexDirection: 'row', +}); + +const removeFileButtonStyles = css({ + marginLeft: spacing[1], +}); + +const buttonStyles = css({ + width: '100%', +}); const errorMessageStyles = css({ color: `${redBaseColor} !important`, @@ -39,31 +47,45 @@ const labelHorizontalStyles = css({ paddingRight: spacing[3], }); -const labelIconStyles = css` - display: inline-block; - vertical-align: middle; - font: normal normal normal 14px/1 FontAwesome; - font-size: inherit; - text-rendering: auto; - margin: 0 0 0 5px; - cursor: pointer; - color: #bfbfbe; - - &:link, - &:active { - color: #bfbfbe; - } - - &:link, - &:active, - &:hover { - text-decoration: none; - } - - &:hover { - color: #fbb129; - } -`; +const optionalLabelStyles = css({ + color: uiColors.gray.base, + fontStyle: 'italic', + fontWeight: 'normal', + fontSize: 12, +}); + +const labelIconStyles = css({ + display: 'inline-block', + verticalAlign: 'middle', + font: 'normal normal normal 14px/1 FontAwesome', + fontSize: 'inherit', + textRendering: 'auto', + margin: '0 0 0 5px', + cursor: 'pointer', + color: '#bfbfbe', + + '&:link, &:active': { + color: '#bfbfbe', + }, + + '&:link, &:active, &:hover': { + textDecoration: 'none', + }, + + '&:hover': { + color: '#fbb129', + }, +}); + +const disabledLabelStyles = css({ + '&:first-child': { + pointerEvents: 'none', + }, +}); + +const disabledDescriptionStyles = css({ + color: uiColors.gray.dark1, +}); export enum Variant { Horizontal = 'HORIZONTAL', @@ -81,10 +103,14 @@ function FileInput({ label, dataTestId, onChange, + disabled, multi = false, + optional = false, + optionalMessage, error = false, errorMessage, variant = Variant.Horizontal, + showFileOnNewLine = false, link, description, values, @@ -92,14 +118,18 @@ function FileInput({ }: { id: string; label: string; - dataTestId: string; + dataTestId?: string; onChange: (files: string[]) => void; + disabled?: boolean; multi?: boolean; + optional?: boolean; + optionalMessage?: string; error?: boolean; errorMessage?: string; variant?: Variant; link?: string; description?: string; + showFileOnNewLine?: boolean; values?: string[]; labelAlignment?: 'right' | 'left' | 'center'; }): React.ReactElement { @@ -124,9 +154,9 @@ function FileInput({ [onChange] ); - const renderDescription = () => { + const renderDescription = (): React.ReactElement | null => { if (!link && !description) { - return <>; + return null; } if (!link) { return ( @@ -165,18 +195,40 @@ function FileInput({ { [formItemVerticalStyles]: variant === Variant.Vertical } )} > - + + {showFileOnNewLine && + values && + values.length > 0 && + values.map((value, index) => ( +
+ {value} + { + const newValues = [...values]; + newValues.splice(index, 1); + onChange(newValues); + }} + > + + +
+ ))} {error && errorMessage && (