Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static_src/react/issue-wizard/DetailsStep.test.tsx b/static_src/react/issue-wizard/DetailsStep.test.tsx
new file mode 100644
index 0000000..eaef0e7
--- /dev/null
+++ b/static_src/react/issue-wizard/DetailsStep.test.tsx
@@ -0,0 +1,34 @@
+// 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, cleanup} from '@testing-library/react';
+import {assert} from 'chai';
+
+import DetailsStep from './DetailsStep.tsx';
+
+describe('DetailsStep', () => {
+  afterEach(cleanup);
+
+  it('renders', async () => {
+    const {container} = render(<DetailsStep />);
+
+    // this is checking for the first question
+    const input = container.querySelector('input');
+    assert.isNotNull(input)
+
+    // this is checking for the rest
+    const count = document.querySelectorAll('textarea').length;
+    assert.equal(count, 3)
+  });
+
+  it('renders category in title', async () => {
+    const {container} = render(<DetailsStep category='UI'/>);
+
+    // this is checking the title contains our category
+    const title = container.querySelector('h2');
+    assert.include(title?.innerText, 'Details for problems with UI');
+  });
+
+});
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/DetailsStep.tsx b/static_src/react/issue-wizard/DetailsStep.tsx
new file mode 100644
index 0000000..1a69cc1
--- /dev/null
+++ b/static_src/react/issue-wizard/DetailsStep.tsx
@@ -0,0 +1,65 @@
+// 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 {createStyles, createTheme} from '@material-ui/core/styles';
+import {makeStyles} from '@material-ui/styles';
+import TextField from '@material-ui/core/TextField';
+import {red, grey} from '@material-ui/core/colors';
+
+/**
+ * The detail step is the second step on the dot
+ * stepper. This react component provides the users with
+ * specific questions about their bug to be filled out.
+ */
+const theme: Theme = createTheme();
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      '& > *': {
+        margin: theme.spacing(1),
+        width: '100%',
+      },
+    },
+    head: {
+        marginTop: '25px',
+    },
+    red: {
+        color: red[600],
+    },
+    grey: {
+        color: grey[600],
+    },
+  }), {defaultTheme: theme}
+);
+
+export default function DetailsStep({textValues, setTextValues, category}:
+  {textValues: Object, setTextValues: Function, category: string}): React.ReactElement {
+  const classes = useStyles();
+
+  const handleChange = (valueName: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
+    const textInput = e.target.value;
+    setTextValues({...textValues, [valueName]: textInput});
+  };
+
+  return (
+    <>
+        <h2 className={classes.grey}>Details for problems with {category}</h2>
+        <form className={classes.root} noValidate autoComplete="off">
+            <h3 className={classes.head}>Please enter a one line summary <span className={classes.red}>*</span></h3>
+            <TextField id="outlined-basic-1" variant="outlined" onChange={handleChange('oneLineSummary')}/>
+
+            <h3 className={classes.head}>Steps to reproduce problem <span className={classes.red}>*</span></h3>
+            <TextField multiline rows={4} id="outlined-basic-2" variant="outlined" onChange={handleChange('stepsToReproduce')}/>
+
+            <h3 className={classes.head}>Please describe the problem <span className={classes.red}>*</span></h3>
+            <TextField multiline rows={3} id="outlined-basic-3" variant="outlined" onChange={handleChange('describeProblem')}/>
+
+            <h3 className={classes.head}>Additional Comments</h3>
+            <TextField multiline rows={3} id="outlined-basic-4" variant="outlined" onChange={handleChange('additionalComments')}/>
+        </form>
+    </>
+  );
+}
diff --git a/static_src/react/issue-wizard/DotMobileStepper.test.tsx b/static_src/react/issue-wizard/DotMobileStepper.test.tsx
new file mode 100644
index 0000000..5203110
--- /dev/null
+++ b/static_src/react/issue-wizard/DotMobileStepper.test.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 {render, screen, cleanup} from '@testing-library/react';
+import {assert} from 'chai';
+
+import DotMobileStepper from './DotMobileStepper.tsx';
+
+describe('DotMobileStepper', () => {
+  let container: HTMLElement;
+
+  afterEach(cleanup);
+
+  it('renders', () => {
+    container = render(<DotMobileStepper activeStep={0} nextEnabled={true}/>).container;
+
+    // this is checking the buttons for the stepper rendered
+      const count = document.querySelectorAll('button').length;
+      assert.equal(count, 2)
+  });
+
+  it('back button disabled on first step', () => {
+    render(<DotMobileStepper activeStep={0} nextEnabled={true}/>).container;
+
+    // Finds a button on the page with "back" as text using React testing library.
+    const backButton = screen.getByRole('button', {name: /backButton/i}) as HTMLButtonElement;
+
+    // Back button is disabled on the first step.
+    assert.isTrue(backButton.disabled);
+  });
+
+  it('both buttons enabled on second step', () => {
+    render(<DotMobileStepper activeStep={1} nextEnabled={true}/>).container;
+
+    // Finds a button on the page with "back" as text using React testing library.
+    const backButton = screen.getByRole('button', {name: /backButton/i}) as HTMLButtonElement;
+
+    // Finds a button on the page with "next" as text using React testing library.
+    const nextButton = screen.getByRole('button', {name: /nextButton/i}) as HTMLButtonElement;
+
+    // Back button is not disabled on the second step.
+    assert.isFalse(backButton.disabled);
+
+    // Next button is not disabled on the second step.
+    assert.isFalse(nextButton.disabled);
+  });
+
+  it('next button disabled on last step', () => {
+    render(<DotMobileStepper activeStep={2}/>).container;
+
+    // Finds a button on the page with "next" as text using React testing library.
+    const nextButton = screen.getByRole('button', {name: /nextButton/i}) as HTMLButtonElement;
+
+    // Next button is disabled on the second step.
+    assert.isTrue(nextButton.disabled);
+  });
+});
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/DotMobileStepper.tsx b/static_src/react/issue-wizard/DotMobileStepper.tsx
new file mode 100644
index 0000000..9870f03
--- /dev/null
+++ b/static_src/react/issue-wizard/DotMobileStepper.tsx
@@ -0,0 +1,72 @@
+// 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 {createTheme} from '@material-ui/core/styles';
+import {makeStyles} from '@material-ui/styles';
+import MobileStepper from '@material-ui/core/MobileStepper';
+import Button from '@material-ui/core/Button';
+import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
+import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight';
+
+const theme: Theme = createTheme();
+
+const useStyles = makeStyles({
+  root: {
+    width: '100%',
+    flexGrow: 1,
+  },
+}, {defaultTheme: theme});
+
+/**
+ * `<DotMobileStepper />`
+ *
+ * React component for rendering the linear dot stepper of the issue wizard.
+ *
+ *  @return ReactElement.
+ */
+export default function DotsMobileStepper({nextEnabled, activeStep, setActiveStep} : {nextEnabled: boolean, activeStep: number, setActiveStep: Function}) : React.ReactElement {
+  const classes = useStyles();
+
+  const handleNext = () => {
+    setActiveStep((prevActiveStep: number) => prevActiveStep + 1);
+  };
+
+  const handleBack = () => {
+    setActiveStep((prevActiveStep: number) => prevActiveStep - 1);
+  };
+
+  let label;
+  let icon;
+
+  if (activeStep === 2){
+    label = 'Submit';
+    icon = '';
+  } else {
+    label = 'Next';
+    icon = <KeyboardArrowRight />;
+  }
+  return (
+    <MobileStepper
+      id="mobile-stepper"
+      variant="dots"
+      steps={3}
+      position="static"
+      activeStep={activeStep}
+      className={classes.root}
+      nextButton={
+        <Button aria-label="nextButton" size="medium" onClick={handleNext} disabled={activeStep === 2 || !nextEnabled}>
+          {label}
+          {icon}
+        </Button>
+      }
+      backButton={
+        <Button aria-label="backButton" size="medium" onClick={handleBack} disabled={activeStep === 0}>
+          <KeyboardArrowLeft />
+          Back
+        </Button>
+      }
+    />
+  );
+}
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/LandingStep.tsx b/static_src/react/issue-wizard/LandingStep.tsx
new file mode 100644
index 0000000..efe6491
--- /dev/null
+++ b/static_src/react/issue-wizard/LandingStep.tsx
@@ -0,0 +1,108 @@
+import React from 'react';
+import {makeStyles, withStyles} from '@material-ui/styles';
+import {blue, yellow, red, grey} from '@material-ui/core/colors';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Checkbox, {CheckboxProps} from '@material-ui/core/Checkbox';
+import SelectMenu from './SelectMenu.tsx';
+import RadioDescription from './RadioDescription.tsx';
+
+const CustomCheckbox = withStyles({
+  root: {
+    color: blue[400],
+    '&$checked': {
+      color: blue[600],
+    },
+  },
+  checked: {},
+})((props: CheckboxProps) => <Checkbox color="default" {...props} />);
+
+const useStyles = makeStyles({
+  pad: {
+    margin: '10px, 20px',
+    display: 'inline-block',
+  },
+  flex: {
+    display: 'flex',
+  },
+  inlineBlock: {
+    display: 'inline-block',
+  },
+  warningBox: {
+    minHeight: '10vh',
+    borderStyle: 'solid',
+    borderWidth: '2px',
+    borderColor: yellow[800],
+    borderRadius: '8px',
+    background: yellow[50],
+    padding: '0px 20px 1em',
+    margin: '30px 0px'
+  },
+  warningHeader: {
+    color: yellow[800],
+    fontSize: '16px',
+    fontWeight: '500',
+  },
+  star:{
+    color: red[700],
+    marginRight: '8px',
+    fontSize: '16px',
+    display: 'inline-block',
+  },
+  header: {
+    color: grey[900],
+    fontSize: '28px',
+    marginTop: '6vh',
+  },
+  subheader: {
+    color: grey[700],
+    fontSize: '18px',
+    lineHeight: '32px',
+  },
+  red: {
+    color: red[600],
+  },
+});
+
+export default function LandingStep({checkExisting, setCheckExisting, userType, setUserType, category, setCategory}:
+  {checkExisting: boolean, setCheckExisting: Function, userType: string, setUserType: Function, category: string, setCategory: Function}) {
+  const classes = useStyles();
+
+  const handleCheckChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    setCheckExisting(event.target.checked);
+  };
+
+  return (
+    <>
+      <p className={classes.header}>Report an issue with Chromium</p>
+      <p className={classes.subheader}>
+        We want you to enter the best possible issue report so that the project team members
+        can act on it effectively. The following steps will help route your issue to the correct
+        people.
+      </p>
+      <p className={classes.subheader}>
+        Please select your following role: <span className={classes.red}>*</span>
+      </p>
+      <RadioDescription value={userType} setValue={setUserType}/>
+      <div className={classes.subheader}>
+        Which of the following best describes the issue that you are reporting? <span className={classes.red}>*</span>
+      </div>
+      <SelectMenu option={category} setOption={setCategory}/>
+      <div className={classes.warningBox}>
+        <p className={classes.warningHeader}> Avoid duplicate issue reports:</p>
+        <div>
+          <div className={classes.star}>*</div>
+          <FormControlLabel className={classes.pad}
+            control={
+              <CustomCheckbox
+                checked={checkExisting}
+                onChange={handleCheckChange}
+                name="warningCheck"
+              />
+            }
+            label="By checking this box, I'm acknowledging that I have searched for existing issues that already report this problem."
+          />
+        </div>
+      </div>
+    </>
+  );
+}
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/RadioDescription.test.tsx b/static_src/react/issue-wizard/RadioDescription.test.tsx
new file mode 100644
index 0000000..ff65eae
--- /dev/null
+++ b/static_src/react/issue-wizard/RadioDescription.test.tsx
@@ -0,0 +1,54 @@
+// 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 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');
+  });
+});
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/RadioDescription.tsx b/static_src/react/issue-wizard/RadioDescription.tsx
new file mode 100644
index 0000000..ad78c78
--- /dev/null
+++ b/static_src/react/issue-wizard/RadioDescription.tsx
@@ -0,0 +1,117 @@
+// 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 userGroups = Object.freeze({
+  END_USER: 'End User',
+  WEB_DEVELOPER: 'Web Developer',
+  CONTRIBUTOR: 'Chromium Contributor',
+});
+
+const BlueRadio = withStyles({
+  root: {
+    color: blue[400],
+    '&$checked': {
+      color: blue[600],
+    },
+  },
+  checked: {},
+})((props: RadioProps) => <Radio color="default" {...props} />);
+
+const useStyles = makeStyles({
+  flex: {
+    display: 'flex',
+    justifyContent: 'space-between',
+  },
+  container: {
+    width: '320px',
+    height: '150px',
+    position: 'relative',
+    display: 'inline-block',
+  },
+  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',
+  }
+});
+
+/**
+ * `<RadioDescription />`
+ *
+ * React component for radio buttons and their descriptions
+ * on the landing step of the Issue Wizard.
+ *
+ *  @return ReactElement.
+ */
+export default function RadioDescription({value, setValue} : {value: string, setValue: Function}): React.ReactElement {
+  const classes = useStyles();
+
+  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    setValue(event.target.value);
+  };
+
+  return (
+    <div className={classes.flex}>
+      <div className={classes.container}>
+        <BlueRadio
+          checked={value === userGroups.END_USER}
+          onChange={handleChange}
+          value={userGroups.END_USER}
+          inputProps={{ 'aria-label': userGroups.END_USER}}
+        />
+        <div className={classes.text}>
+          <p className={classes.title}>{userGroups.END_USER}</p>
+          <p className={classes.subheader}>I am a user trying to do something on a website.</p>
+        </div>
+        <hr color={grey[200]} className={classes.line}/>
+      </div>
+      <div className={classes.container}>
+        <BlueRadio
+          checked={value === userGroups.WEB_DEVELOPER}
+          onChange={handleChange}
+          value={userGroups.WEB_DEVELOPER}
+          inputProps={{ 'aria-label': userGroups.WEB_DEVELOPER }}
+        />
+        <div className={classes.text}>
+          <p className={classes.title}>{userGroups.WEB_DEVELOPER}</p>
+          <p className={classes.subheader}>I am a web developer trying to build something.</p>
+        </div>
+        <hr color={grey[200]} className={classes.line}/>
+      </div>
+      <div className={classes.container}>
+        <BlueRadio
+          checked={value === userGroups.CONTRIBUTOR}
+          onChange={handleChange}
+          value={userGroups.CONTRIBUTOR}
+          inputProps={{ 'aria-label': userGroups.CONTRIBUTOR }}
+        />
+        <div className={classes.text}>
+          <p className={classes.title}>{userGroups.CONTRIBUTOR}</p>
+          <p className={classes.subheader}>I know about a problem in specific tests or code.</p>
+        </div>
+        <hr color={grey[200]} className={classes.line}/>
+      </div>
+    </div>
+    );
+  }
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/SelectMenu.test.tsx b/static_src/react/issue-wizard/SelectMenu.test.tsx
new file mode 100644
index 0000000..13efef6
--- /dev/null
+++ b/static_src/react/issue-wizard/SelectMenu.test.tsx
@@ -0,0 +1,38 @@
+// 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} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import {screen} from '@testing-library/dom';
+import {assert} from 'chai';
+
+import SelectMenu from './SelectMenu.tsx';
+
+describe('SelectMenu', () => {
+  let container: React.RenderResult;
+
+  beforeEach(() => {
+    container = render(<SelectMenu />).container;
+  });
+
+  it('renders', () => {
+    const form = container.querySelector('form');
+    assert.isNotNull(form)
+  });
+
+  it('renders options on click', () => {
+    const input = document.getElementById('outlined-select-category');
+    if (!input) {
+      throw new Error('Input is undefined');
+    }
+
+    userEvent.click(input)
+
+    // 14 is the current number of options in the select menu
+    const count = screen.getAllByTestId('select-menu-item').length;
+
+    assert.equal(count, 14);
+  });
+});
\ No newline at end of file
diff --git a/static_src/react/issue-wizard/SelectMenu.tsx b/static_src/react/issue-wizard/SelectMenu.tsx
new file mode 100644
index 0000000..3b0b96d
--- /dev/null
+++ b/static_src/react/issue-wizard/SelectMenu.tsx
@@ -0,0 +1,133 @@
+// 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 {createTheme, Theme} from '@material-ui/core/styles';
+import {makeStyles} from '@material-ui/styles';
+import MenuItem from '@material-ui/core/MenuItem';
+import TextField from '@material-ui/core/TextField';
+
+const CATEGORIES = [
+  {
+    value: 'UI',
+    label: 'UI',
+  },
+  {
+    value: 'Accessibility',
+    label: 'Accessibility',
+  },
+  {
+    value: 'Network/Downloading',
+    label: 'Network/Downloading',
+  },
+  {
+    value: 'Audio/Video',
+    label: 'Audio/Video',
+  },
+  {
+    value: 'Content',
+    label: 'Content',
+  },
+  {
+    value: 'Apps',
+    label: 'Apps',
+  },
+  {
+    value: 'Extensions/Themes',
+    label: 'Extensions/Themes',
+  },
+  {
+    value: 'Webstore',
+    label: 'Webstore',
+  },
+  {
+    value: 'Sync',
+    label: 'Sync',
+  },
+  {
+    value: 'Enterprise',
+    label: 'Enterprise',
+  },
+  {
+    value: 'Installation',
+    label: 'Installation',
+  },
+  {
+    value: 'Crashes',
+    label: 'Crashes',
+  },
+  {
+    value: 'Security',
+    label: 'Security',
+  },
+  {
+    value: 'Other',
+    label: 'Other',
+  },
+];
+
+const theme: Theme = createTheme();
+
+const useStyles = makeStyles((theme: Theme) => ({
+  container: {
+    display: 'flex',
+    flexWrap: 'wrap',
+    maxWidth: '65%',
+  },
+  textField: {
+    marginLeft: theme.spacing(1),
+    marginRight: theme.spacing(1),
+  },
+  menu: {
+    width: '100%',
+    minWidth: '300px',
+  },
+}), {defaultTheme: theme});
+
+/**
+ * Select menu component that is located on the landing step if the
+ * Issue Wizard. The menu is used for the user to indicate the category
+ * of their bug when filing an issue.
+ *
+ * @return ReactElement.
+ */
+export default function SelectMenu({option, setOption}: {option: string, setOption: Function}) {
+  const classes = useStyles();
+  const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
+    setOption(event.target.value as string);
+  };
+
+  return (
+    <form className={classes.container} noValidate autoComplete="off">
+      <TextField
+        id="outlined-select-category"
+        select
+        label=''
+        className={classes.textField}
+        value={option}
+        onChange={handleChange}
+        InputLabelProps={{shrink: false}}
+        SelectProps={{
+          MenuProps: {
+            className: classes.menu,
+          },
+        }}
+        margin="normal"
+        variant="outlined"
+        fullWidth={true}
+      >
+      {CATEGORIES.map(option => (
+        <MenuItem
+          className={classes.menu}
+          key={option.value}
+          value={option.value}
+          data-testid="select-menu-item"
+        >
+           {option.label}
+        </MenuItem>
+       ))}
+      </TextField>
+    </form>
+  );
+}
\ No newline at end of file