Merge branch 'main' into avm99963-monorail

GitOrigin-RevId: 2bbe35caf837ba29cc5c1a89d66a6881c1230034
diff --git a/static_src/react/issue-wizard/RadioDescription/RadioDescription.test.tsx b/static_src/react/issue-wizard/RadioDescription/RadioDescription.test.tsx
new file mode 100644
index 0000000..296e449
--- /dev/null
+++ b/static_src/react/issue-wizard/RadioDescription/RadioDescription.test.tsx
@@ -0,0 +1,67 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import React from 'react';
+import { render, screen, cleanup } from '@testing-library/react';
+import userEvent from '@testing-library/user-event'
+import { assert } from 'chai';
+import sinon from 'sinon';
+
+import { RadioDescription } from './RadioDescription.tsx';
+
+describe('RadioDescription', () => {
+  afterEach(cleanup);
+
+  it('renders', () => {
+    render(<RadioDescription />);
+    // look for blue radios
+    const radioOne = screen.getByRole('radio', { name: /Web Developer/i });
+    assert.isNotNull(radioOne)
+
+    const radioTwo = screen.getByRole('radio', { name: /End User/i });
+    assert.isNotNull(radioTwo)
+
+    const radioThree = screen.getByRole('radio', { name: /Chromium Contributor/i });
+    assert.isNotNull(radioThree)
+  });
+
+  it('checks selected radio value', () => {
+    // We're passing in the "Web Developer" value here manually
+    // to tell our code that that radio button is selected.
+    render(<RadioDescription value={'Web Developer'} />);
+
+    const checkedRadio = screen.getByRole('radio', { name: /Web Developer/i });
+    assert.isTrue(checkedRadio.checked);
+
+    // Extra check to make sure we haven't checked every single radio button.
+    const uncheckedRadio = screen.getByRole('radio', { name: /End User/i });
+    assert.isFalse(uncheckedRadio.checked);
+  });
+
+  it('sets radio value when radio button is clicked', () => {
+    // Using the sinon.js testing library to create a function for testing.
+    const setValue = sinon.stub();
+
+    render(<RadioDescription setValue={setValue} />);
+
+    const radio = screen.getByRole('radio', { name: /Web Developer/i });
+    userEvent.click(radio);
+
+    // Asserts that "Web Developer" was passed into our "setValue" function.
+    sinon.assert.calledWith(setValue, 'Web Developer');
+  });
+
+  it('sets radio value when any part of the parent RoleSelection is clicked', () => {
+    const setValue = sinon.stub();
+
+    render(<RadioDescription setValue={setValue} />);
+
+    // Click text in the RoleSelection component
+    const p = screen.getByText('End User');
+    userEvent.click(p);
+
+    // Asserts that "End User" was passed into our "setValue" function.
+    sinon.assert.calledWith(setValue, 'End User');
+  });
+});
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/RadioDescription/RadioDescription.tsx b/static_src/react/issue-wizard/RadioDescription/RadioDescription.tsx
new file mode 100644
index 0000000..9a5a7d2
--- /dev/null
+++ b/static_src/react/issue-wizard/RadioDescription/RadioDescription.tsx
@@ -0,0 +1,59 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import React from 'react';
+import { makeStyles } from '@material-ui/styles';
+import { RoleSelection } from './RoleSelection/RoleSelection.tsx';
+
+const userGroups = Object.freeze({
+  END_USER: 'End User',
+  WEB_DEVELOPER: 'Web Developer',
+  CONTRIBUTOR: 'Chromium Contributor',
+});
+
+const useStyles = makeStyles({
+  flex: {
+    display: 'flex',
+    justifyContent: 'space-between',
+  }
+});
+
+/**
+ * RadioDescription contains a set of radio buttons and descriptions (RoleSelection)
+ * to be chosen from in the landing step of the Issue Wizard.
+ *
+ * @returns React.ReactElement
+ */
+export const RadioDescription = ({ value, setValue }: { value: string, setValue: Function }): React.ReactElement => {
+  const classes = useStyles();
+
+  const handleRoleSelectionClick = (userGroup: string) =>
+    (event: React.MouseEvent<HTMLElement>) => setValue(userGroup)
+
+  return (
+    <div className={classes.flex}>
+      <RoleSelection
+        checked={value === userGroups.END_USER}
+        handleOnClick={handleRoleSelectionClick(userGroups.END_USER)}
+        value={userGroups.END_USER}
+        description="I am a user trying to do something on a website."
+        inputProps={{ 'aria-label': userGroups.END_USER }}
+      />
+      <RoleSelection
+        checked={value === userGroups.WEB_DEVELOPER}
+        handleOnClick={handleRoleSelectionClick(userGroups.WEB_DEVELOPER)}
+        value={userGroups.WEB_DEVELOPER}
+        description="I am a web developer trying to build something."
+        inputProps={{ 'aria-label': userGroups.WEB_DEVELOPER }}
+      />
+      <RoleSelection
+        checked={value === userGroups.CONTRIBUTOR}
+        handleOnClick={handleRoleSelectionClick(userGroups.CONTRIBUTOR)}
+        value={userGroups.CONTRIBUTOR}
+        description="I know about a problem in specific tests or code."
+        inputProps={{ 'aria-label': userGroups.CONTRIBUTOR }}
+      />
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/RadioDescription/RoleSelection/RoleSelection.tsx b/static_src/react/issue-wizard/RadioDescription/RoleSelection/RoleSelection.tsx
new file mode 100644
index 0000000..803a7b7
--- /dev/null
+++ b/static_src/react/issue-wizard/RadioDescription/RoleSelection/RoleSelection.tsx
@@ -0,0 +1,95 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import React from 'react';
+import { makeStyles, withStyles } from '@material-ui/styles';
+import { blue, grey } from '@material-ui/core/colors';
+import Radio, { RadioProps } from '@material-ui/core/Radio';
+
+const useStyles = makeStyles({
+  container: {
+    width: '320px',
+    height: '150px',
+    position: 'relative',
+    display: 'inline-block',
+    cursor: 'pointer',
+  },
+  text: {
+    position: 'absolute',
+    display: 'inline-block',
+    left: '55px',
+  },
+  title: {
+    marginTop: '7px',
+    fontSize: '20px',
+    color: grey[900],
+  },
+  subheader: {
+    fontSize: '16px',
+    color: grey[800],
+  },
+  line: {
+    position: 'absolute',
+    bottom: 0,
+    width: '300px',
+    left: '20px',
+  }
+});
+
+const BlueRadio = withStyles({
+  root: {
+    color: blue[400],
+    '&$checked': {
+      color: blue[600],
+    },
+  },
+  checked: {},
+})((props: RadioProps) => <Radio color="default" {...props} />);
+
+interface RoleSelectionProps {
+  /* Whether or not the radio button should be checked */
+  checked: boolean
+  /* onClick callback defined in parent component */
+  handleOnClick: (event: React.MouseEvent<HTMLElement>) => void
+  /*
+      A string representing the type of user; this is the value of the input
+      see `userGroups`, which is defined in RadioDescription
+  */
+  value: string
+  /* Descriptive text to be displayed along with the radio button */
+  description: string
+  /* Additional props for the radio button component */
+  inputProps: { [key: string]: string }
+}
+
+/**
+ * RoleSelection encapsulates the radio button and details
+ * for selecting a role as an issue reporter in the issue wizard
+ * @see RadioDescription
+ */
+
+export const RoleSelection = ({
+  checked,
+  handleOnClick,
+  value,
+  description,
+  inputProps
+}: RoleSelectionProps): React.ReactElement => {
+  const classes = useStyles();
+  return (
+    <div className={classes.container} onClick={handleOnClick}>
+      <BlueRadio
+        checked={checked}
+        value={value}
+        inputProps={inputProps}
+      />
+      <div className={classes.text}>
+        <p className={classes.title}>{value}</p>
+        <p className={classes.subheader}>{description}</p>
+      </div>
+      <hr color={grey[200]} className={classes.line} />
+    </div>
+  )
+}
+