React-材质-UI-秘籍-五-

58 阅读43分钟

React 材质 UI 秘籍(五)

原文:zh.annas-archive.org/md5/c4e5ed8c3a8a54c4065e4c907829dab6

译者:飞龙

协议:CC BY-NC-SA 4.0

第十三章:选择 - 从选项中进行选择

在本章中,你将了解以下内容:

  • 抽象复选框组

  • 自定义复选框项

  • 抽象单选按钮组

  • 使用单选按钮类型

  • 将复选框替换为开关

  • 使用状态控制选择

  • 选择多个项目

简介

任何包含用户交互的应用程序都涉及用户进行选择。这可以从简单的开/关开关到允许选择多个项目的多个项目选择。Material-UI 有不同类型的选择组件,最适合特定的用户场景。

抽象复选框组

复选框通常向用户提供一组相关的选项,用户可以选择或取消选择。Material-UI 的Checkbox组件提供了基本的功能,但你可能想要一个更高级别的功能,可以在整个应用程序中重用。

如何实现...

让我们为复选框选项组创建一个抽象。以下是CheckboxGroup组件的代码:

import React, { useState } from 'react';

import FormLabel from '@material-ui/core/FormLabel';
import FormControl from '@material-ui/core/FormControl';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormHelperText from '@material-ui/core/FormHelperText';
import Checkbox from '@material-ui/core/Checkbox';

const CheckboxGroup = ({ values, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <FormGroup>
      {values.map((value, index) => (
        <FormControlLabel
          key={index}
          control={
            <Checkbox
              checked={value.checked}
              onChange={onChange(index)}
            />
          }
          label={value.label}
        />
      ))}
    </FormGroup>
  </FormControl>
);

export default function AbstractingCheckboxGroups() {
  const [values, setValues] = useState([
    { label: 'First', checked: false },
    { label: 'Second', checked: false },
    { label: 'Third', checked: false }
  ]);

  const onChange = index => ({ target: { checked } }) => {
    const newValues = [...values];
    const value = values[index];

    newValues[index] = { ...value, checked };

    setValues(newValues);
  };

  return (
    <CheckboxGroup
      label="Choices"
      values={values}
      onChange={onChange}
    />
  );
}

当你首次加载屏幕时,你会看到以下内容:

图片

当你选择前两个选项时,它看起来是这样的:

图片

它是如何工作的...

让我们更详细地看看CheckboxGroup组件:

const CheckboxGroup = ({ values, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <FormGroup>
      {values.map((value, index) => (
        <FormControlLabel
          key={index}
          control={
            <Checkbox
              checked={value.checked}
              onChange={onChange(index)}
            />
          }
          label={value.label}
        />
      ))}
    </FormGroup>
  </FormControl>
);

这是允许你在应用程序的各个屏幕上渲染复选框选项组的抽象。有几个 Material-UI 组件涉及到渲染一组复选框——CheckboxGroup为你处理这些,所以你只需要传递一个包含valueslabelonChange处理程序的数组。

接下来,让我们看看你的应用程序组件是如何渲染CheckboxGroup的:

<CheckboxGroup
  label="Choices"
  values={values}
  onChange={onChange}
/>

你只需要关注结构化值数组,并在你的应用程序需要渲染一组相关复选框选项时将其传递给CheckboxGroup组件。最后,让我们看看state和用于切换值选中状态的onChange()处理程序:

const [values, setValues] = useState([
  { label: 'First', checked: false },
  { label: 'Second', checked: false },
  { label: 'Third', checked: false }
]);

const onChange = index => ({ target: { checked } }) => {
  const newValues = [...values];
  const value = values[index];

  newValues[index] = { ...value, checked };

  setValues(newValues);
};

checked属性根据索引参数和target.checked值进行更改。

还有更多...

让我们在这个例子中添加一个List组件,这样你可以更好地可视化当复选框被选中/取消选中时发生的状态变化。以下是你需要导入的附加 Material-UI 组件:

import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import Typography from '@material-ui/core/Typography';

策略是让这个列表渲染已选中项的标签。让我们在CheckboxGroup组件下方渲染这个列表:

<Fragment>
  <CheckboxGroup
    label="Choices"
    values={values}
    onChange={onChange}
  />
  <Typography variant="h6">Selection</Typography>
  <List>
    {values
      .filter(value => value.checked)
      .map((value, index) => (
        <ListItem key={index}>
          <ListItemText>{value.label}</ListItemText>
        </ListItem>
      ))}
  </List>
</Fragment>

values上的filter()调用只会包含checked属性为true的值。当屏幕首次加载时,你会看到一个空列表,因为没有默认选中任何内容:

图片

当你开始进行选择时,你会看到选择列表随着应用程序状态的变化而变化:

图片

参见

定制复选框项

Material-UI Checkbox组件的默认外观试图与原生的浏览器复选框输入元素相似。你可以更改组件的选中和不选中状态所使用的图标。即使你更改了Checkbox使用的图标,任何颜色更改仍然会被尊重。

如何做到这一点...

下面是一些导入几个 Material-UI 图标并使用它们来配置Checkbox组件使用的图标的代码:

import React, { useState, useEffect } from 'react';

import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';

import AccountBalance from '@material-ui/icons/AccountBalance';
import AccountBalanceOutlined from '@material-ui/icons/AccountBalanceOutlined';
import Backup from '@material-ui/icons/Backup';
import BackupOutlined from '@material-ui/icons/BackupOutlined';
import Build from '@material-ui/icons/Build';
import BuildOutlined from '@material-ui/icons/BuildOutlined';

const initialItems = [
  {
    name: 'AccountBalance',
    Icon: AccountBalanceOutlined,
    CheckedIcon: AccountBalance
  },
  {
    name: 'Backup',
    Icon: BackupOutlined,
    CheckedIcon: Backup
  },
  {
    name: 'Build',
    Icon: BuildOutlined,
    CheckedIcon: Build
  }
];

export default function CustomizingCheckboxItems() {
  const [items, setItems] = useState({});

  useEffect(() => {
    setItems(
      initialItems.reduce(
        (state, item) => ({ ...state, [item.name]: false }),
        {}
      )
    );
  }, []);

  const onChange = e => {
    setItems({ [e.target.name]: e.target.checked });
  };

  return (
    <FormGroup>
      {initialItems.map(({ name, Icon, CheckedIcon }, index) => (
        <FormControlLabel
          key={index}
          control={
            <Checkbox
              checked={items[name]}
              onChange={onChange}
              inputProps={{ name }}
              icon={<Icon fontSize="small" />}
              checkedIcon={<CheckedIcon fontSize="small" />}
            />
          }
          label={name}
        />
      ))}
    </FormGroup>
  );
}

当屏幕首次加载时,复选框看起来是这样的:

图片

这些复选框是未选中的。当它们被选中时,它们看起来是这样的:

图片

它是如何工作的...

让我们一步步分析这里发生的事情。initialItems数组是构建复选框的起点:

const initialItems = [
  {
    name: 'AccountBalance',
    Icon: AccountBalanceOutlined,
    CheckedIcon: AccountBalance
  },
  {
    name: 'Backup',
    Icon: BackupOutlined,
    CheckedIcon: Backup
  },
  {
    name: 'Build',
    Icon: BuildOutlined,
    CheckedIcon: Build
  }
];

每个项目都有一个name组件来标识复选框,以及选中/未选中的Icon组件。接下来,让我们看看CustomizingCheckboxItems组件的状态是如何初始化的:

const [items, setItems] = useState({});

useEffect(() => {
  setItems(
    initialItems.reduce(
      (state, item) => ({ ...state, [item.name]: false }),
      {}
    )
  );
}, []);

状态通过减少initialItems数组初始化为一个对象。对于数组中的每个项目,该组件的状态将有一个属性初始化为 false。属性的名称基于项目的name属性。例如,组件状态在减少后可能看起来像这样:

{
  AccountBalance: false,
  Backup: false,
  Build: false
}

这些属性用于存储每个复选框的选中状态。接下来,让我们看看每个Checkbox组件是如何根据initialItems数组渲染的:

<FormGroup>
  {initialItems.map(({ name, Icon, CheckedIcon }, index) => (
    <FormControlLabel
      key={index}
      control={
        <Checkbox
          checked={items[name]}
          onChange={onChange}
          inputProps={{ name }}
          icon={<Icon fontSize="small" />}
          checkedIcon={<CheckedIcon fontSize="small" />}
        />
      }
      label={name}
    />
  ))}
</FormGroup>

定制每个复选框的关键属性是iconcheckedIcon。这些属性分别使用项目数组中的IconCheckIcon属性。

还有更多...

由于你用来定制Checkbox组件的图标是 Material-UI 组件,你可以更改复选框的颜色,并且它将像没有自定义图标一样工作。例如,你可以将此示例中复选框的颜色设置为默认值:

<Checkbox
  color="default"
  checked={items[name]}
  onChange={onChange}
  inputProps={{ name }}
  icon={<Icon fontSize="small" />}
  checkedIcon={<CheckedIcon fontSize="small" />}
/>

这是所有复选框都被选中时的样子:

图片

当复选框从未选中变为选中时,颜色设置为默认值,颜色不会改变。不过,这并不重要,因为图标从轮廓主题变为填充主题。仅形状的变化就足以表明项目已被选中。

让我们尝试一下primary,只是为了好玩:

<Checkbox
  color="primary"
  checked={items[name]}
  onChange={onChange}
  inputProps={{ name }}
  icon={<Icon fontSize="small" />}
  checkedIcon={<CheckedIcon fontSize="small" />}
/>

如果所有选项都被选中,看起来是这样的:

图片

参见

抽象单选按钮组

单选按钮组与复选框组类似。关键区别在于单选按钮用于只应选择一个值的情况。此外,与复选框组一样,单选按钮组需要几个可以在整个应用程序中封装和重用的 Material-UI 组件。

它是如何工作的...

下面是一些代码,它捕获了将单选按钮组组合成一个组件所需的所有组件:

import React, { useState } from 'react';

import Radio from '@material-ui/core/Radio';
import { default as MaterialRadioGroup } from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';

const options = [
  { label: 'First', value: 'first' },
  { label: 'Second', value: 'second' },
  { label: 'Third', value: 'third' }
];

const RadioGroup = ({ value, options, name, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <MaterialRadioGroup
      name={name}
      value={value}
      onChange={onChange}
      disabled
    >
      {options.map((option, index) => (
        <FormControlLabel
          key={index}
          control={<Radio />}
          value={option.value}
          label={option.label}
        />
      ))}
    </MaterialRadioGroup>
  </FormControl>
);

export default function AbstractingRadioButtonGroups() {
  const [value, setValue] = useState('first');

  const onChange = e => {
    setValue(e.target.value);
  };

  return (
    <RadioGroup
      value={value}
      options={options}
      name="radio1"
      label="Pick One"
      onChange={onChange}
    />
  );
}

当您首次加载屏幕时,您会看到以下内容:

图片

如果您点击第三个选项,组件看起来是这样的:

图片

因为这些选项都属于同一个单选组,所以一次只能选择一个选项。

它是如何工作的...

让我们更仔细地看看这个例子中的RadioGroup组件:

const RadioGroup = ({ value, options, name, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <MaterialRadioGroup name={name} value={value} onChange={onChange}>
      {options.map((option, index) => (
        <FormControlLabel
          key={index}
          control={<Radio />}
          value={option.value}
          label={option.label}
        />
      ))}
    </MaterialRadioGroup>
  </FormControl>
);

options属性应该有一个数组值,然后映射到FormControlLabel组件。control属性使用Radio组件来渲染每个单选控制。与复选框组不同,onChange属性位于MaterialRadioGroup组件上,而不是每个单独的Radio上。这是因为只有一个活动值,由MaterialRadioGroup管理。

由于我们正在创建一个同名的组件,所以使用MaterialRadioGroup别名导入 Material-UI 的RadioGroup组件。只要您清楚哪些包拥有哪些组件,这就可以了。

接下来,让我们看看RadioGroup组件是如何渲染的:

<RadioGroup
  value={value}
  options={options}
  name="radio1"
  label="Pick One"
  onChange={onChange}
/>

name属性是连接一切的关键。确保同一组中的单选按钮具有相同的名称非常重要。这种抽象通过只要求在一个地方提供名称来为您处理这个问题。下面是options数组的样子:

const options = [
  { label: 'First', value: 'first' },
  { label: 'Second', value: 'second' },
  { label: 'Third', value: 'third' }
];

单选组的理念是它们始终只有一个值。options 数组中的值属性是允许的值——但只有一个处于活动状态。在下面的例子中,最后要查看的是 onChange 处理器和应用程序组件的状态结构:

const [value, setValue] = useState('first');

const onChange = e => {
  setValue(e.target.value);
};

这是设置初始单选选择的步骤。当它改变时,值状态会更新为所选单选按钮的值。

更多内容...

你可以通过在 FormControl 组件上设置 disabled 属性来禁用整个单选按钮组:

<FormControl component="fieldset" disabled>
  ...
</FormControl>

当你禁用控件时,你将无法与之交互。下面是这种情况的示例:

图片

在其他场景中,你可能只想禁用一个选项。你可以在 RadioGroup 组件中通过检查 options 数组中的 disabled 属性来实现这一点:

<FormControlLabel
  key={index}
  control={<Radio disabled={option.disabled} />}
  value={option.value}
  label={option.label}
/>

这是如何在 options 数组中禁用选项的方法:

const options = [
  { label: 'First', value: 'first' },
  { label: 'Second', value: 'second', disabled: true },
  { label: 'Third', value: 'third' }
];

这是禁用第二个选项后单选组的样式:

图片

当第二个选项被禁用时,没有方法可以激活它,因为用户无法与之交互。

注意禁用默认激活的选项。这可能会使用户感到困惑。你可以激活组中的另一个选项,但之后你将无法激活最初激活的选项。

参见

单选按钮类型

有许多单选按钮方面可以自定义,以创建您自己的单选按钮组。虽然从多个选项中选择单个值的基本原则没有改变,但您可以设计单选按钮组以适应任何应用程序。

如何实现...

假设根据你屏幕的布局,并且为了与你的应用程序中的其他屏幕保持一致,你需要创建一个具有以下设计特性的单选组:

  • 单行用于展示选项

  • 每个选项都有图标和文本

  • 主要主题颜色用于选中的选项

下面是一些实现此功能的代码:

import React, { Fragment, useState } from 'react';

import Radio from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';

import Car from '@material-ui/icons/DirectionsCar';
import CarOutlined from '@material-ui/icons/DirectionsCarOutlined';
import Bus from '@material-ui/icons/DirectionsBus';
import BusOutlined from '@material-ui/icons/DirectionsBusOutlined';
import Train from '@material-ui/icons/Train';
import TrainOutlined from '@material-ui/icons/TrainOutlined';

export default function RadioButtonTypes() {
  const [value, setValue] = useState('train');

  const onChange = e => {
    setValue(e.target.value);
  };

  return (
    <FormControl component="fieldset">
      <FormLabel component="legend">Travel Mode</FormLabel>
      <RadioGroup name="travel" value={value} onChange={onChange} row>
        <FormControlLabel
          value="car"
          control={
            <Radio
              color="primary"
              icon={<CarOutlined />}
              checkedIcon={<Car />}
            />
          }
          label="Car"
          labelPlacement="bottom"
        />
        <FormControlLabel
          value="bus"
          control={
            <Radio
              color="primary"
              icon={<BusOutlined />}
              checkedIcon={<Bus />}
            />
          }
          label="Bus"
          labelPlacement="bottom"
        />
        <FormControlLabel
          value="train"
          control={
            <Radio
              color="primary"
              icon={<TrainOutlined />}
              checkedIcon={<Train />}
            />
          }
          label="Train"
          labelPlacement="bottom"
        />
      </RadioGroup>
    </FormControl>
  );
}

这是屏幕首次加载时单选组的样式:

图片

你可以通过点击其他图标或标签来更改默认选择。图标状态会更新以反映更改:

图片

它是如何工作的...

看起来我们能够满足为单选按钮组设定的标准。让我们通过代码来查看每个要求是如何满足的。首先,组是水平渲染的,每个单选按钮都在同一行。这是通过向RadioGroup组件传递row属性来实现的:

<RadioGroup
  name="travel"
  value={value}
  onChange={onChange}
  row
>

每个单选按钮的标签都显示在每个单选按钮下方,因为这与组的行布局更协调。这是通过设置FormControlLabellabelPlacement属性值来实现的。当单选按钮被选中时,它使用 Material-UI 主题的默认颜色。它还使用自定义图标来表示选中状态和未选中状态:

<Radio
  color="primary"
  icon={<BusOutlined />}
  checkedIcon={<Bus />}
/>

这两个增强功能都是由Radio组件处理的。

参见

用开关替换复选框

Material-UI 有一个与复选框非常相似的控制组件,称为开关。这两个组件之间的主要视觉区别是开关更强调开关/关断动作。在移动环境中,用户可能更习惯于Switch组件。在其他任何环境中,你可能最好坚持使用常规的Checkbox组件。

如何做到这一点...

假设你不想创建一个抽象一组Checkbox组件的组件,而是想用Switch组件做同样的事情。以下是代码:

import React, { Fragment, useState } from 'react';

import FormLabel from '@material-ui/core/FormLabel';
import FormControl from '@material-ui/core/FormControl';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormHelperText from '@material-ui/core/FormHelperText';
import Switch from '@material-ui/core/Switch';

const SwitchGroup = ({ values, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <FormGroup>
      {values.map((value, index) => (
        <FormControlLabel
          key={index}
          control={
            <Switch
              checked={value.checked}
              onChange={onChange(index)}
            />
          }
          label={value.label}
        />
      ))}
    </FormGroup>
  </FormControl>
);

export default function ReplacingCheckboxesWithSwitches() {
  const [values, setValues] = useState([
    { label: 'First', checked: false },
    { label: 'Second', checked: false },
    { label: 'Third', checked: false }
  ]);

  const onChange = index => ({ target: { checked } }) => {
    const newValues = [...values];
    const value = values[index];

    newValues[index] = { ...value, checked };
    setValues(newValues);
  };

  return (
    <SwitchGroup
      label="Choices"
      values={values}
      onChange={onChange}
    />
  );
}

这是屏幕首次加载时开关组的外观:

图片

这是所有开关都打开时开关组的外观:

图片

它是如何工作的...

在任何可以使用Checkbox组件的地方,你同样可以使用Switch组件。这段代码是从本章早期部分的“抽象复选框组”部分摘取的。Checkbox组件被替换成了Switch组件。

更多内容...

而不是为处理 CheckboxSwitch 组件编写不同的代码路径,你可以增强 SwitchGroup 组件以接受一个 checkbox 布尔属性,当 true 时,使用 Checkbox 作为控制而不是 Switch。以下是新的 SwitchGroup 的外观:

const SwitchGroup = ({ values, label, onChange }) => (
  <FormControl component="fieldset">
    <FormLabel component="legend">{label}</FormLabel>
    <FormGroup>
      {values.map((value, index) => (
        <FormControlLabel
          key={index}
          control={
            <Switch
              checked={value.checked}
              onChange={onChange(index)}
            />
          }
          label={value.label}
        />
      ))}
    </FormGroup>
  </FormControl>
);

以下是一个示例,展示了两种版本的控件并排渲染:

<Fragment>
  <SwitchGroup
    label="Switch Choices"
    values={values}
    onChange={this.onChange}
  />
  <SwitchGroup
    label="Switch Choices"
    values={values}
    onChange={onChange}
    checkbox
  />
</Fragment>

第二个 SwitchGroup 组件使用 checkbox 属性来渲染 Checkbox 组件而不是 Switch 组件。以下是结果的外观:

图片

如果你选择开关选项组或复选框选项组中的第一个选项,你会看到以下内容:

图片

它们都进行了更新,因为这两个领域共享相同的应用状态。

参见

使用状态控制选择框

一些表单涉及从值列表中进行选择。这有点像从单选按钮组中选择单选按钮选项。使用 Material-UI Select 组件,你得到的东西更像是传统的 HTML 选择元素。通常,Web 应用程序表单有几个相互依赖的选择框。在 React/Material-UI 应用程序中,这些选择框通过 state 组件进行控制。

如何实现...

假设你的屏幕上有两个选择框——一个类别选择框和一个产品选择框。最初,只有类别选择框被填充并启用。产品选择框依赖于类别选择框——一旦选择了一个类别,产品选择框就会被启用并填充适当的产 品。以下是实现此功能的代码:

import React, { Fragment, useState } from 'react';

import { makeStyles } from '@material-ui/styles';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';

const useStyles = makeStyles(theme => ({
  control: { margin: theme.spacing(2), minWidth: 200 }
}));

export default function ControllingSelectsWithState() {
  const classes = useStyles();

  const [categories, setCategories] = useState([
    { label: 'Category 1', id: 1 },
    { label: 'Category 2', id: 2 },
    { label: 'Category 3', id: 3 }
  ]);

  const [products, setProducts] = useState([
    { label: 'Product 1', id: 1, category: 1 },
    { label: 'Product 2', id: 2, category: 1 },
    { label: 'Product 3', id: 3, category: 1 },
    { label: 'Product 4', id: 4, category: 2 },
    { label: 'Product 5', id: 5, category: 2 },
    { label: 'Product 6', id: 6, category: 2 },
    { label: 'Product 7', id: 7, category: 3 },
    { label: 'Product 8', id: 8, category: 3 },
    { label: 'Product 9', id: 9, category: 3 }
  ]);

  const setters = {
    categories: setCategories,
    products: setProducts
  };
  const collections = { categories, products };

  const onChange = e => {
    const setCollection = setters[e.target.name];
    const collection = collections[e.target.name].map(item => ({
      ...item,
      selected: false
    }));
    const index = collection.findIndex(
      item => item.id === e.target.value
    );

    collection[index] = { ...collection[index], selected: true };
    setCollection(collection);
  };

  const category = categories.find(category => category.selected) || {
    id: ''
  };
  const product = products.find(product => product.selected) || {
    id: ''
  };

  return (
    <Fragment>
      <FormControl className={classes.control}>
        <InputLabel htmlFor="categories">Category</InputLabel>
        <Select
          value={category.id}
          onChange={onChange}
          inputProps={{
            name: 'categories',
            id: 'categories'
          }}
        >
          <MenuItem value="">
            <em>None</em>
          </MenuItem>
          {categories.map(category => (
            <MenuItem key={category.id} value={category.id}>
              {category.label}
            </MenuItem>
          ))}
        </Select>
      </FormControl>
      <FormControl
        className={classes.control}
        disabled={category.id === ''}
      >
        <InputLabel htmlFor="Products">Product</InputLabel>
        <Select
          value={product.id}
          onChange={onChange}
          inputProps={{
            name: 'products',
            id: 'values'
          }}
        >
          <MenuItem value="">
            <em>None</em>
          </MenuItem>
          {products
            .filter(product => product.category === category.id)
            .map(product => (
              <MenuItem key={product.id} value={product.id}>
                {product.label}
              </MenuItem>
            ))}
        </Select>
      </FormControl>
    </Fragment>
  );
}

当屏幕首次加载时,你会看到以下内容:

图片

类别选择框填充了可供你选择的选项。产品选择框处于禁用状态,因为没有选择任何类别。以下是类别选择框打开时的外观:

图片

一旦你选择了一个类别,你应该能够打开产品选择框并做出产品选择:

图片

它是如何工作的...

本例中的两个 Select 组件存在状态依赖。也就是说,产品选择的状 态依赖于类别选择的状 态。这是因为产品选择中显示的选项是根据所选类别进行筛选的。让我们更仔细地看看这个状态:

const [categories, setCategories] = useState([
  { label: 'Category 1', id: 1 },
  { label: 'Category 2', id: 2 },
  { label: 'Category 3', id: 3 }
]);

const [products, setProducts] = useState([
  { label: 'Product 1', id: 1, category: 1 },
  { label: 'Product 2', id: 2, category: 1 },
  { label: 'Product 3', id: 3, category: 1 },
  { label: 'Product 4', id: 4, category: 2 },
  { label: 'Product 5', id: 5, category: 2 },
  { label: 'Product 6', id: 6, category: 2 },
  { label: 'Product 7', id: 7, category: 3 },
  { label: 'Product 8', id: 8, category: 3 },
  { label: 'Product 9', id: 9, category: 3 }
]);

categoriesproducts 数组代表屏幕上两个选择框的选项。选中的选项用 selected 布尔属性值 true 标记。默认情况下没有选项被选中。两个选择框都使用相同的 onChange() 处理器:

const setters = {
  categories: setCategories,
  products: setProducts
};
const collections = { categories, products };

const onChange = e => {
  const setCollection = setters[e.target.name];
  const collection = collections[e.target.name].map(item => ({
    ...item,
    selected: false
  }));
  const index = collection.findIndex(
    item => item.id === e.target.value
  );

  collection[index] = { ...collection[index], selected: true };
  setCollection(collection);
};

要使用的数组取决于e.target.name的值——它将是类别或产品。一旦使用适当的数组初始化了集合值,selected属性就被设置为每个值的false。然后,根据e.target.value查找选中的值,并将selected属性设置为true

接下来,让我们分析ControllingSelectsWithState组件其余部分发生的情况。首先,从组件状态中查找categoryproduct的选中项:

const category = categories.find(category => category.selected) || {
  id: ''
};
const product = products.find(product => product.selected) || {
  id: ''
};

您必须确保始终将这些常量分配给具有id属性的对象,因为稍后会有所期待。空字符串将匹配空值选项,因此它默认被选中。接下来,让我们看看类别选项是如何渲染的:

{categories.map(category => (
  <MenuItem key={category.id} value={category.id}>
    {category.label}
  </MenuItem>
))}

这是对categories数组中的值到MenuItem组件的直接映射。选择category中的选项永远不会改变;换句话说,产品选项根据选中的类别改变——让我们看看这是如何实现的:

{products
  .filter(product => product.category === category.id)
  .map(product => (
    <MenuItem key={product.id} value={product.id}>
      {product.label}
    </MenuItem>
  ))}

在将每个产品映射到MenuItem组件之前,使用filter()根据选中的类别过滤products数组。

相关内容

选择多个项目

用户可以从Select组件中选择多个值。这涉及到使用数组作为选中的值状态。

如何实现...

这里有一些渲染具有多个值的Select的代码。您可以选择您喜欢的任意多个值:

import React, { useState } from 'react';

import { makeStyles } from '@material-ui/styles';
import Select from '@material-ui/core/Select';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';

const options = [
  { id: 1, label: 'First' },
  { id: 2, label: 'Second' },
  { id: 3, label: 'Third' },
  { id: 4, label: 'Fourth' },
  { id: 5, label: 'Fifth' }
];

const useStyles = makeStyles(theme => ({
  formControl: {
    margin: theme.spacing(1),
    minWidth: 100,
    maxWidth: 280
  }
}));

export default function SelectingMultipleItems() {
  const classes = useStyles();
  const [selected, setSelected] = useState([]);

  const onChange = e => {
    setSelected(e.target.value);
  };

  return (
    <FormControl className={classes.formControl}>
      <InputLabel htmlFor="multi">Value</InputLabel>
      <Select
        multiple
        value={selected}
        onChange={onChange}
        input={<Input id="multi" />}
      >
        {options.map(option => (
          <MenuItem key={option.id} value={option.id}>
            {option.label}
          </MenuItem>
        ))}
      </Select>
    </FormControl>
  );
}

这是首次打开选择时的样子:

图片

这是选中了第一个、第三个和第五个选项时的样子:

图片

现在您已经做出了选择,您可以在菜单外的屏幕上点击某个位置来关闭它,或者您可以按E**sc 键。您将在文本输入中看到您的选择:

图片

它是如何工作的...

让我们从查看Select组件是如何渲染的开始:

<Select
  multiple
  value={selected}
  onChange={onChange}
  input={<Input id="multi" />}
>
  {options.map(option => (
    <MenuItem key={option.id} value={option.id}>
      {option.label}
    </MenuItem>
  ))}
</Select>

options 数组的值被映射到 MenuItem 组件,就像任何其他的 Select 一样。multiple 属性告诉组件允许用户进行多项选择。SelectingMultipleItems 组件的 selected 状态是一个数组,它包含选中值。这个数组由 onChange 处理程序填充:

const onChange = e => {
  setSelected(e.target.value);
};

因为使用了 multiple 属性,e.target.value 是一个包含选中值的数组——你可以直接使用这个值来更新选中状态。

还有更多...

而不是让选中的项目以逗号分隔的 test 列表形式显示,你可以通过将选中的值映射到 Chip 组件来使项目突出。让我们创建一个将处理此功能的组件:

function Selected({ selected }) {
  const classes = useStyles();

  return selected.map(value => (
    <Chip
      key={value}
      label={options.find(option => option.id === value).label}
      className={classes.chip}
    />
  ));
}

以下代码块展示了如何在 Select 组件的 renderValue 属性中使用此组件:

<Select
  multiple
  value={selected}
  onChange={onChange}
  input={<Input id="multi" />}
  renderValue={selected => <Selected selected={selected} />}
>
  {options.map(option => (
    <MenuItem key={option.id} value={option.id}>
      {option.label}
    </MenuItem>
  ))}
</Select>

现在,当你进行多项选择时,它们将以 Chip 组件的形式渲染:

参见

第十四章:选择器 - 选择日期和时间

在本章中,我们将涵盖以下主题:

  • 使用日期选择器

  • 使用时间选择器

  • 设置初始日期和时间值

  • 结合日期和时间组件

  • 集成其他日期和时间包

简介

大多数应用程序都需要允许用户选择日期和时间值。例如,如果表单包含一个调度部分,用户需要一个直观的方式来选择日期和时间值。在 Material-UI 应用程序中,你可以使用库中提供的日期和时间选择器组件。

使用日期选择器

要在 Material-UI 应用程序中使用日期选择器,你可以利用TextField组件。它接受一个type属性,你可以将其设置为date。然而,除了更改文本字段类型之外,你还需要注意其他一些事情。

如何实现...

下面是一段代码,用于为用户渲染一个日期选择器文本字段,并在日期选择改变时显示另一个格式的文本字段:

import React, { Fragment, useState } from 'react';

import { makeStyles } from '@material-ui/styles';
import TextField from '@material-ui/core/TextField';

const useStyles = makeStyles(theme => ({
  textField: { margin: theme.spacing(1) }
}));

export default function UsingDatePickers() {
  const classes = useStyles();
  const [date, setDate] = useState('');

  const onChange = e => {
    setDate(e.target.value);
  };

  const dateFormatted = date
    ? new Date(`${date}T00:00:00`).toLocaleDateString()
    : null;

  return (
    <Fragment>
      <TextField
        value={date}
        onChange={onChange}
        label="My Date"
        type="date"
        className={classes.textField}
        InputLabelProps={{
          shrink: true
        }}
      />
      <TextField
        value={dateFormatted}
        label="Updated Date Value"
        className={classes.textField}
        InputLabelProps={{
          shrink: true
        }}
        InputProps={{ readOnly: true }}
      />
    </Fragment>
  );
}

当页面首次加载时,你会看到以下内容:

图片

左侧的“我的日期”字段是日期选择器。右侧的“更新日期值”字段以不同的格式显示选定的日期。以下是日期选择器在获得焦点时的外观:

图片

日期的年份部分被突出显示。你可以输入年份,或者可以使用上下箭头按钮来更改选定的值。通过按Tab键或使用鼠标指针,你可以切换到日期的月份或日部分。最右侧的向下箭头在点击时会显示以下原生浏览器日期选择器:

图片

一旦你选择了日期,下面是“我的日期”和“更新日期值”字段的外观:

图片

它是如何工作的...

让我们先看看日期选择器TextField组件:

<TextField
  value={date}
  onChange={onChange}
  label="My Date"
  type="date"
  className={classes.textField}
  InputLabelProps={{
    shrink: true
  }}
/>

大多数日期选择器功能来自设置为datetype属性。这应用了输入掩码和原生浏览器日期选择器控件。由于输入掩码值,shrink输入属性需要设置为true以避免重叠。value属性来自UsingDatePickers组件的状态。此值默认为空字符串,但需要以特定格式。日期选择器文本字段将把日期值放入正确的格式,因此onChange()处理程序实际上不需要做任何事情,只需设置date状态即可。

“更新日期值”字段使用不同的日期格式。让我们看看这是如何实现的:

const dateFormatted = date
  ? new Date(`${date}T00:00:00`).toLocaleDateString()
  : null;

首先,你需要从组件状态中获取date字符串,并使用它来构造一个新的Date实例。为此,你需要将时间字符串附加到日期字符串上。这使得它成为一个有效的 ISO 字符串,并使得日期可以无任何意外地构造。现在你可以使用任何可用的日期格式化函数,例如toLocaleDateString()

现在,你可以将dateFormatted传递给第二个文本字段,该字段是只读的,因为它只用于显示值:

<TextField
  value={dateFormatted}
  label="Updated Date Value"
  className={classes.textField}
  InputLabelProps={{
    shrink: true
  }}
  InputProps={{ readOnly: true }}
/>

还有更多...

可以对前面的示例进行一些改进。首先,你可以有一个DatePicker组件,它隐藏了一些将TextField组件转换为选择日期的组件的细节。此外,如果新的DatePicker组件支持实际的Date实例作为值,那就更好了。

首先,你需要一个实用函数,可以将Date实例格式化为TextField组件作为日期选择器使用时预期的字符串格式:

function formatDate(date) {
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const day = date.getDate();

  return [
    year,
    month < 10 ? `0${month}` : month,
    day < 10 ? `0${day}` : day
  ].join('-');
}

formatDate()函数接受一个Date实例,并返回一个格式为YYYY-MM-dd的字符串。现在,你已经准备好构建DatePicker组件了:

const DatePicker = ({ date, ...props }) => (
  <TextField
    value={date instanceof Date ? formatDate(date) : date}
    type="date"
    InputLabelProps={{
      shrink: true
    }}
    {...props}
  />
);

DatePicker组件渲染一个TextField组件。它将type属性值设置为date,将shrink输入属性设置为true。它还设置了value属性——首先检查date属性是否是Date实例,如果是,则调用formatDate()。否则,直接使用date参数。

现在,让我们用DatePicker组件替换前面示例中的TextField组件:

<Fragment>
  <DatePicker
    date={date}
    onChange={onChange}
    label="My Date"
    className={classes.textField}
  />
  <TextField
    value={dateFormatted}
    label="Updated Date Value"
    className={classes.textField}
    InputLabelProps={{
      shrink: true
    }}
    InputProps={{ readOnly: true }}
  />
</Fragment>

onChangelabelclassName属性以与之前相同的方式传递给TextField组件。与DatePicker组件的主要区别是,你不需要传递typeInputProps,而是使用date而不是value

参见

使用时间选择器

与日期选择器一样,时间选择器帮助用户输入时间值。同样,Material-UI 应用程序中的时间选择器也是从TextInput组件派生出来的。

如何实现...

让我们创建与使用日期选择器部分中使用的相同抽象,但这次是为了time选择器:

import React, { Fragment, useState } from 'react';

import { makeStyles } from '@material-ui/styles';
import TextField from '@material-ui/core/TextField';

const useStyles = makeStyles(theme => ({
  textField: { margin: theme.spacing(1) }
}));

const TimePicker = ({ time, ...props }) => (
  <TextField
    value={time}
    type="time"
    InputLabelProps={{
      shrink: true
    }}
    inputProps={{
      step: 300
    }}
    {...props}
  />
);

export default function UsingTimePickers() {
  const classes = useStyles();
  const [time, setTime] = useState('');

  const onChange = e => {
    setTime(e.target.value);
  };

  return (
    <Fragment>
      <TimePicker
        time={time}
        onChange={onChange}
        label="My Time"
        className={classes.textField}
      />
      <TextField
        value={time}
        label="Updated Time Value"
        className={classes.textField}
        InputLabelProps={{
          shrink: true
        }}
        InputProps={{ readOnly: true }}
      />
    </Fragment>
  );
}

页面首次加载时,你会看到以下内容:

图片

当 My Time 字段获得焦点后,你可以使用上/下箭头键或显示在时间值右侧的上/下箭头按钮来更改单个时间部分:

图片

更新时间值字段不会更新,直到在 My Time 字段中选择了完整的时间,因为在此发生之前没有时间值:

图片

它是如何工作的...

TimePicker组件的结构与上一道菜谱中的DatePicker组件非常相似。主要区别在于TimePicker不支持Date实例,因为它只处理时间。因为没有日期部分,使用Date实例仅表达时间比仅表达日期要困难得多:

const TimePicker = ({ time, ...props }) => (
  <TextField
    value={time}
    type="time"
    InputLabelProps={{
      shrink: true
    }}
    inputProps={{
      step: 300
    }}
    {...props}
  />
);

TimePicker组件在TextField上设置的属性与DatePicker组件相同。此外,step值为300使得时间部分的分钟数每次移动五分钟。

参见

设置初始日期和时间值

日期和时间选择器可以分别具有默认的日期和时间值。例如,一个常见的场景是让这些输入默认为当前日期和时间。

如何实现...

假设你在应用的屏幕上有一个日期选择器和时间选择器。你希望date字段默认为当前日期,time字段默认为当前时间。为此,最好依赖于Date实例来设置初始的Date/Time值。然而,这需要一点工作,因为你不能原生地将Date实例传递给TextField组件。以下是一个示例,说明这是如何工作的:

import React, { Fragment, useState } from 'react';

import { makeStyles } from '@material-ui/styles';
import TextField from '@material-ui/core/TextField';

const useStyles = makeStyles(theme => ({
  textField: { margin: theme.spacing.unit }
}));

function formatDate(date) {
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const day = date.getDate();

  return [
    year,
    month < 10 ? `0${month}` : month,
    day < 10 ? `0${day}` : day
  ].join('-');
}

function formatTime(date) {
  const hours = date.getHours();
  const minutes = date.getMinutes();

  return [
    hours < 10 ? `0${hours}` : hours,
    minutes < 10 ? `0${minutes}` : minutes
  ].join(':');
}

const DatePicker = ({ date, ...props }) => (
  <TextField
    value={date instanceof Date ? formatDate(date) : date}
    type="date"
    InputLabelProps={{
      shrink: true
    }}
    {...props}
  />
);

const TimePicker = ({ time, ...props }) => (
  <TextField
    value={time instanceof Date ? formatTime(time) : time}
    type="time"
    InputLabelProps={{
      shrink: true
    }}
    inputProps={{
      step: 300
    }}
    {...props}
  />
);

export default function SettingInitialDateAndTimeValues() {
  const classes = useStyles();
  const [datetime, setDatetime] = useState(new Date());

  const onChangeDate = e => {
    if (!e.target.value) {
      return;
    }

    const [year, month, day] = e.target.value
      .split('-')
      .map(n => Number(n));

    const newDatetime = new Date(datetime);
    newDatetime.setYear(year);
    newDatetime.setMonth(month - 1);
    newDatetime.setDate(day);

    setDatetime(newDatetime);
  };

  const onChangeTime = e => {
    const [hours, minutes] = e.target.value
      .split(':')
      .map(n => Number(n));

    const newDatetime = new Date(datetime);
    newDatetime.setHours(hours);
    newDatetime.setMinutes(minutes);

    setDatetime(newDatetime);
  };

  return (
    <Fragment>
      <DatePicker
        date={datetime}
        onChange={onChangeDate}
        label="My Date"
        className={classes.textField}
      />
      <TimePicker
        time={datetime}
        onChange={onChangeTime}
        label="My Time"
        className={classes.textField}
      />
    </Fragment>
  );
}

当屏幕首次加载时,你会看到以下内容:

图片

你看到的日期和时间将取决于你何时加载屏幕。然后你可以更改日期和时间值。

它是如何工作的...

这种方法的优点是,你只需要处理一个state,即datetime,它是一个Date实例。让我们逐步查看代码,看看这是如何实现的,从UsingDatePickers组件的初始状态开始:

const [datetime, setDatetime] = useState(new Date());

当前日期和时间被分配给datetime状态。接下来,让我们看看两个格式化函数,它们使Date实例能够与TextField组件一起工作:

function formatDate(date) {
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const day = date.getDate();

  return [
    year,
    month < 10 ? `0${month}` : month,
    day < 10 ? `0${day}` : day
  ].join('-');
}

function formatTime(date) {
  const hours = date.getHours();
  const minutes = date.getMinutes();

  return [
    hours < 10 ? `0${hours}` : hours,
    minutes < 10 ? `0${minutes}` : minutes
  ].join(':');
}

这两个函数,formatDate()formatTime(),都接受一个Date实例作为参数,并返回一个与TextField组件一起工作的字符串格式值。接下来,让我们看看onChangeDate()处理程序:

const onChangeDate = e => {
  if (!e.target.value) {
    return;
  }

  const [year, month, day] = e.target.value
    .split('-')
    .map(n => Number(n));

  const newDatetime = new Date(datetime);
  newDatetime.setYear(year);
  newDatetime.setMonth(month - 1);
  newDatetime.setDate(day);

  setDatetime(newDatetime);
};

onChangeDate()中发生的第一个检查是对value属性的检查。这个检查之所以需要发生,是为了让日期选择器实际上允许用户选择一个无效的日期,比如 2 月 31 日。当选择这个无效日期时,不改变state实际上是在防止选择无效日期。

接下来,yearmonthday 值被分割并映射到数字。然后,通过使用 datetime 作为值创建一个新的 Date 实例来初始化新的 newDatetime 值。这样做是为了保留时间选择。最后,使用 setYear()setMonth()setDate() 更新 Date 实例而不改变时间。

最后,让我们来看看 onChangeTime() 处理器:

const onChangeTime = e => {
  const [hours, minutes] = e.target.value
    .split(':')
    .map(n => Number(n));

  const newDatetime = new Date(datetime);
  newDatetime.setHours(hours);
  newDatetime.setMinutes(minutes);

  setDatetime(newDatetime);
};

onChangeTime() 处理器遵循与 onChangeDate() 相同的一般模式。它更简单,因为值更少,且不需要检查无效的时间 - 每天都有 24 小时。

参见

合并日期和时间组件

如果你的应用程序需要从用户那里收集日期和时间,你不必一定需要两个 TextField 组件。相反,你可以将它们合并成一个字段。

如何实现...

你可以通过将 type 属性设置为 datetime-local 来使用单个 TextInput 组件收集用户的日期和时间输入:

import React, { Fragment, useState } from 'react';

import { makeStyles } from '@material-ui/styles';
import TextField from '@material-ui/core/TextField';

const useStyles = makeStyles(theme => ({
  textField: { margin: theme.spacing(1) }
}));

const formatDate = date =>
  date
    .toISOString()
    .split(':')
    .slice(0, 2)
    .join(':');

const DateTimePicker = ({ date, ...props }) => (
  <TextField
    value={
      date instanceof Date
        ? date.toISOString().replace('Z', '')
        : date
    }
    type="datetime-local"
    InputLabelProps={{
      shrink: true
    }}
    {...props}
  />
);

export default function CombiningDateAndTimeComponents() {
  const classes = useStyles();
  const [datetime, setDatetime] = useState(new Date());

  const onChangeDate = e => {
    setDatetime(new Date(`${e.target.value}Z`));
  };

  return (
    <DateTimePicker
      date={formatDate(datetime)}
      onChange={onChangeDate}
      label="My Date/Time"
      className={classes.textField}
    />
  );
}

当屏幕首次加载时,你会看到以下内容:

图片

这是字段获得焦点并且显示更改日期/时间控件时的外观:

图片

它是如何工作的...

当你使用 datetime-local 类型的输入时,它简化了与 Date 实例的工作。让我们看看 onChangeDate() 处理器:

const onChangeDate = e => {
  setDatetime(new Date(`${e.target.value}Z`));
};

你可以将 e.target.value 作为参数传递给一个新的 Date 实例,然后它将成为新的 datetime 状态值。最后,让我们看看用于将正确值传递给 TextFieldvalue 属性的 formatDate() 函数:

const formatDate = date =>
  date
    .toISOString()
    .split(':')
    .slice(0, 2)
    .join(':');

使用此函数的原因是删除 value 属性中的秒和毫秒。否则,这些将显示为用户可以选择的输入字段中的值。当选择时间时,用户选择秒或毫秒的情况非常罕见。

参见

集成其他日期和时间包

你不必只使用 TextField 组件在你的 Material-UI 应用程序中进行 日期/时间 选择。有可用的包可以让 日期/时间 选择体验更接近传统的 Material Design 组件。

如何实现...

material-ui-pickers 包包含一个 DatePicker 组件和一个 TimePicker 组件。以下是一些代码示例,展示了如何使用这两个组件:

import React, { useState } from 'react';
import 'date-fns';
import DateFnsUtils from '@date-io/date-fns';

import { makeStyles } from '@material-ui/styles';
import Grid from '@material-ui/core/Grid';

import {
  MuiPickersUtilsProvider,
  TimePicker,
  DatePicker
} from 'material-ui-pickers';

const useStyles = makeStyles(theme => ({
  grid: {
    width: '65%'
  }
}));

export default function IntegratingWithOtherDateAndTimePackages() {
  const classes = useStyles();
  const [datetime, setDatetime] = useState(new Date());

  const onChange = datetime => {
    setDatetime(datetime);
  };

  return (
    <MuiPickersUtilsProvider utils={DateFnsUtils}>
      <Grid container className={classes.grid} justify="space-around">
        <DatePicker
          margin="normal"
          label="Date picker"
          value={datetime}
          onChange={onChange}
        />
        <TimePicker
          margin="normal"
          label="Time picker"
          value={datetime}
          onChange={onChange}
        />
      </Grid>
    </MuiPickersUtilsProvider>
  );
}

当屏幕首次加载时,你会看到以下内容:

图片

当你点击日期选择器字段时,你会看到以下内容:

图片

你可以使用这个对话框来选择你的日期,然后点击“确定”来更改它。当你点击时间选择器字段时,你会看到以下内容:

图片

它是如何工作的...

来自 material-ui-pickers 包的 DatePickerTimePicker 组件显示的对话框可以渲染其他 Material-UI 组件,这使得选择日期/时间更加容易。你无需直接与文本输入进行交互,可以展示给用户这样的对话框,这些对话框的主题与你的应用程序的其他部分相匹配,并提供视觉交互来选择日期/时间。

相关内容

第十五章:对话框 - 用户交互的模态屏幕

在本章中,我们将涵盖以下主题:

  • 收集表单输入

  • 确认操作

  • 显示警报

  • API 集成

  • 创建全屏对话框

  • 滚动对话框内容

简介

在与应用程序的交互过程中,用户将不得不在某个时刻向应用程序提供一些信息,做出是/否的决定,或确认重要信息。Material-UI 有一个对话框组件,非常适合这些场景——当你需要一个不会干扰当前屏幕内容的模态显示时。

收集表单输入

当你需要从用户那里收集输入,但又不想失去当前屏幕时,对话框很有用。例如,用户正在查看显示项目列表的屏幕,并想创建一个新项目。对话框可以显示必要的表单字段,一旦创建了新项目,对话框就会关闭,用户就会回到他们的项目列表。

如何操作...

假设你的应用程序允许创建新用户。例如,从显示用户列表的屏幕中,用户点击一个按钮,显示包含创建新用户字段的对话框。以下是如何操作的示例:

import React, { Fragment, useState } from 'react';

import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import Snackbar from '@material-ui/core/Snackbar';

export default function CollectingFormInput() {
  const [dialogOpen, setDialogOpen] = useState(false);
  const [snackbarOpen, setSnackbarOpen] = useState(false);
  const [snackbarMessage, setSnackbarMessage] = useState('');
  const [first, setFirst] = useState('');
  const [last, setLast] = useState('');
  const [email, setEmail] = useState('');

  const onDialogOpen = () => {
    setDialogOpen(true);
  };

  const onDialogClose = () => {
    setDialogOpen(false);
    setFirst('');
    setLast('');
    setEmail('');
  };

  const onSnackbarClose = (e, reason) => {
    if (reason === 'clickaway') {
      return;
    }

    setSnackbarOpen(false);
    setSnackbarMessage('');
  };

  const onCreate = () => {
    setSnackbarOpen(true);
    setSnackbarMessage(`${first} ${last} created`);
    onDialogClose();
  };

  return (
    <Fragment>
      <Button color="primary" onClick={onDialogOpen}>
        New User
      </Button>
      <Dialog open={dialogOpen} onClose={onDialogClose}>
        <DialogTitle>New User</DialogTitle>
        <DialogContent>
          <TextField
            autoFocus
            margin="normal"
            label="First Name"
            InputProps={{ name: 'first' }}
            onChange={e => setFirst(e.target.value)}
            value={first}
            fullWidth
          />
          <TextField
            margin="normal"
            label="Last Name"
            InputProps={{ name: 'last' }}
            onChange={e => setLast(e.target.value)}
            value={last}
            fullWidth
          />
          <TextField
            margin="normal"
            label="Email Address"
            type="email"
            InputProps={{ name: 'email' }}
            onChange={e => setEmail(e.target.value)}
            value={email}
            fullWidth
          />
        </DialogContent>
        <DialogActions>
          <Button onClick={onDialogClose} color="primary">
            Cancel
          </Button>
          <Button
            variant="contained"
            onClick={onCreate}
            color="primary"
          >
            Create
          </Button>
        </DialogActions>
      </Dialog>
      <Snackbar
        open={snackbarOpen}
        message={snackbarMessage}
        onClose={onSnackbarClose}
        autoHideDuration={4000}
      />
    </Fragment>
  );
}

这是屏幕首次加载时你会看到的按钮:

图片

这是点击 NEW USER 按钮时你会看到的对话框:

图片

然后,你可以填写创建新用户的三个字段,并点击 CREATE 按钮。对话框将关闭,你将看到以下Snackbar组件显示:

图片

它是如何工作的...

对话框和 Snackbar 的可见性由布尔状态值dialogOpensnackbarOpen分别控制。dialog组件内字段的值也存储在CollectingFormInput组件的状态中。让我们更仔细地看看dialog标记:

<Dialog open={dialogOpen} onClose={onDialogClose}>
  <DialogTitle>New User</DialogTitle>
  <DialogContent>
    <TextField
      autoFocus
      margin="normal"
      label="First Name"
      InputProps={{ name: 'first' }}
      onChange={e => setFirst(e.target.value)}
      value={first}
      fullWidth
    />
    <TextField
      margin="normal"
      label="Last Name"
      InputProps={{ name: 'last' }}
      onChange={e => setLast(e.target.value)}
      value={last}
      fullWidth
    />
    <TextField
      margin="normal"
      label="Email Address"
      type="email"
      InputProps={{ name: 'email' }}
      onChange={e => setEmail(e.target.value)}
      value={email}
      fullWidth
    />
  </DialogContent>
  <DialogActions>
    <Button onClick={onDialogClose} color="primary">
      Cancel
    </Button>
    <Button
      variant="contained"
      onClick={onCreate}
      color="primary"
    >
      Create
    </Button>
  </DialogActions>
</Dialog>

Dialog组件是其他几个组件的父组件,这些组件构成了dialog的不同部分。DialogTitle组件渲染对话框标题,而DialogActions组件用于在对话框底部渲染操作按钮。DialogContent组件用于渲染对话框的主要内容——创建新用户的三个文本字段。

对于这些TextField组件,有两个属性与在对话框内渲染相关。首先,fullWidth属性将字段水平扩展,使其与对话框宽度相同。这通常与只有几个字段的表单配合得很好。其次,margin属性设置为normal,这为对话框中的字段提供了适当的垂直间距。

接下来,让我们浏览这个组件的事件处理器,从onDialogOpen()开始:

const onDialogOpen = () => {
  setDialogOpen(true);
};

这将通过将 dialogOpen 状态更改为 true 来显示对话框。接下来,让我们看看 onDialogClose()

const onDialogClose = () => {
  setDialogOpen(false);
  setFirst('');
  setLast('');
  setEmail('');
};

这将通过将 dialogOpen 状态设置为 false 来关闭对话框。它还将表单字段值重置为空字符串,以便在下一次对话框显示时为空。接下来,我们有 onSnackbarClose()

const onSnackbarClose = (e, reason) => {
  if (reason === 'clickaway') {
    return;
  }

  setSnackbarOpen(false);
  setSnackbarMessage('');
};

如果 reason 参数是 clickaway,则无需操作。否则,snackbarOpen 状态将更改为 false,这将隐藏 snackbar。snackbarMessage 状态设置为空字符串,以防 snackbar 在未设置新消息的情况下打开。最后,我们有 onCreate() 处理器:

const onCreate = () => {
  setSnackbarOpen(true);
  setSnackbarMessage(`${first} ${last} created`);
  onDialogClose();
};

这将通过将 snackbarOpen 设置为 true 来显示 snackbar。它还设置了包含访问 firstlast 状态值的 snackbarMessage 值。然后,调用 onDialogClose() 来隐藏对话框并重置表单字段。由于 autoHideDuration 值设置为 4000,snackbar 在四秒后关闭。

相关内容

确认操作

确认对话框充当用户的安全网。当用户即将执行可能具有潜在危险的操作时,它们很有用,但不是针对应用中每个可想象的操作。如果执行后无法撤销,则操作可以被认为是危险的。删除账户或处理付款的操作就是一个危险的例子。在这些情况下,您应该始终使用确认对话框。

如何操作...

确认对话框应该简单明了,以便用户可以轻松阅读即将发生的事情,并决定是否取消操作或继续。以下是一些在执行操作前显示确认对话框的代码示例:

import React, { Fragment, useState } from 'react';

import Button from '@material-ui/core/Button';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogActions from '@material-ui/core/DialogActions';
import Dialog from '@material-ui/core/Dialog';

export default function ConfirmingActions() {
  const [open, setOpen] = useState(false);

  const onShowConfirm = () => {
    setOpen(true);
  };

  const onConfirm = () => {
    setOpen(false);
  };

  return (
    <Fragment>
      <Button color="primary" onClick={onShowConfirm}>
        Confirm Action
      </Button>
      <Dialog
        disableBackdropClick
        disableEscapeKeyDown
        maxWidth="xs"
        open={open}
      >
        <DialogTitle>Confirm Delete Asset</DialogTitle>
        <DialogContent>
          <DialogContentText>
            Are you sure you want to delete the asset? This action
            cannot be undone.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button onClick={onDialogClose} color="primary">
            Cancel
          </Button>
          <Button
            variant="contained"
            onClick={onConfirm}
            color="primary"
          >
            Confirm
          </Button>
        </DialogActions>
      </Dialog>
    </Fragment>
  );
}

当通过点击“确认”按钮显示确认对话框时,它看起来是这样的:

您可以点击取消对话框操作来关闭对话框而不做任何事情,或者点击确认对话框操作,这将实际执行操作然后再关闭对话框。

它是如何工作的...

DialogContentText 组件用于在对话框中渲染确认消息。它实际上只是一个围绕 Typography 组件的薄包装。传递给对话框组件的两个有趣的属性是 disableBackdropClickdisableEscapeKeyDown,它们分别防止通过点击对话框外的屏幕或按 Esc 键来关闭确认对话框。

这两个属性的想法是让用户明确承认他们正在执行需要他们密切注意的操作,或者他们选择不执行该操作。

相关内容

显示警报

警报对话框类似于确认对话框。你可以把警报看作是真正重要的 snackbars,不能被忽视。像确认一样,警报会引起干扰,并且必须明确承认才能消除它们。此外,警报对话框可能不是用户采取直接行动的直接结果。警报可以是用户交互的环境发生变化的结果。

如何做...

假设你的应用程序需要能够提醒用户当他们的磁盘空间即将用尽时。以下是一个示例,展示了警报可能的样子:

import React, { Fragment, useState } from 'react';

import Button from '@material-ui/core/Button';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogActions from '@material-ui/core/DialogActions';
import Dialog from '@material-ui/core/Dialog';

export default function ConfirmingActions() {
  const [open, setOpen] = useState(false);

  return (
    <Fragment>
      <Button color="primary" onClick={() => setOpen(true)}>
        Show Alert
      </Button>
      <Dialog open={open}>
        <DialogContent>
          <DialogContentText>
            Disk space critically low. You won't be able to perform
            any actions until you free up some space by deleting
            assets.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button
            variant="contained"
            onClick={() => setOpen(false)}
            color="primary"
          >
            Got It
          </Button>
        </DialogActions>
      </Dialog>
    </Fragment>
  );
}

当你点击显示警报按钮时,这就是警报对话框的显示效果:

它是如何工作的...

警报与常规对话框没有太大区别,你使用它们来收集用户的输入。警报的原则是保持简短并直截了当。例如,这个警报对话框没有标题。它不需要标题就能传达要点——如果用户不开始删除内容,他们将无法做任何事情。

还有更多...

你可以通过在警报消息和关闭警报的按钮上添加图标来让你的警报更加引人注目。以下是一个修改后的示例:

import React, { Fragment, useState } from 'react';

import { makeStyles } from '@material-ui/styles';
import Button from '@material-ui/core/Button';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogActions from '@material-ui/core/DialogActions';
import Dialog from '@material-ui/core/Dialog';
import Grid from '@material-ui/core/Grid';

import WarningIcon from '@material-ui/icons/Warning';
import CheckIcon from '@material-ui/icons/Check';

const useStyles = makeStyles(theme => ({
  rightIcon: {
    marginLeft: theme.spacing(1)
  }
}));

export default function ConfirmingActions() {
  const classes = useStyles();
  const [open, setOpen] = useState(false);

  return (
    <Fragment>
      <Button color="primary" onClick={() => setOpen(true)}>
        Show Alert
      </Button>
      <Dialog open={open}>
        <DialogContent>
          <Grid container>
            <Grid item xs={2}>
              <WarningIcon fontSize="large" color="secondary" />
            </Grid>
            <Grid item xs={10}>
              <DialogContentText>
                Disk space critically low. You won't be able to
                perform any actions until you free up some space by
                deleting assets.
              </DialogContentText>
            </Grid>
          </Grid>
        </DialogContent>
        <DialogActions>
          <Button
            variant="contained"
            onClick={() => setOpen(false)}
            color="primary"
          >
            Got It
            <CheckIcon className={classes.rightIcon} />
          </Button>
        </DialogActions>
      </Dialog>
    </Fragment>
  );
}

这是新警报的样子:

相关内容

API 集成

对话框通常需要从 API 端点提供数据。挑战是在用户等待后台加载 API 数据的同时显示加载状态。

如何实现...

假设你的应用程序需要显示一个带有 Select 组件的对话框来选择一个项目。选择框的选项是从 API 端点填充的,因此你需要处理用户打开对话框和 API 数据到达之间的延迟。以下是一个示例,展示了实现这一点的其中一种方法:

import React, { Fragment, useState } from 'react';

import { makeStyles } from '@material-ui/styles';
import Button from '@material-ui/core/Button';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogActions from '@material-ui/core/DialogActions';
import Dialog from '@material-ui/core/Dialog';
import LinearProgress from '@material-ui/core/LinearProgress';
import MenuItem from '@material-ui/core/MenuItem';
import Select from '@material-ui/core/Select';

const useStyles = makeStyles(theme => ({
  dialog: { minHeight: 200 },
  select: { width: '100%' }
}));

const fetchItems = () =>
  new Promise(resolve => {
    setTimeout(() => {
      resolve([
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' }
      ]);
    }, 3000);
  });

const MaybeLinearProgress = ({ loading, ...props }) =>
  loading ? <LinearProgress {...props} /> : null;

const MaybeSelect = ({ loading, ...props }) =>
  loading ? null : <Select {...props} />;

export default function APIIntegration() {
  const classes = useStyles();
  const [open, setOpen] = useState(false);
  const [loading, setLoading] = useState(false);
  const [items, setItems] = useState([]);
  const [selected, setSelected] = useState('');

  const onShowItems = () => {
    setOpen(true);
    setLoading(true);

    fetchItems().then(items => {
      setLoading(false);
      setItems(items);
    });
  };

  const onClose = () => {
    setOpen(false);
  };

  const onSelect = e => {
    setSelected(e.target.value);
  };

  return (
    <Fragment>
      <Button color="primary" onClick={onShowItems}>
        Select Item
      </Button>
      <Dialog
        open={open}
        classes={{ paper: classes.dialog }}
        maxWidth="xs"
        fullWidth
      >
        <DialogTitle>Select Item</DialogTitle>
        <DialogContent>
          <MaybeLinearProgress loading={loading} />
          <MaybeSelect
            value={selected}
            onChange={onSelect}
            className={classes.select}
            loading={loading}
          >
            <MenuItem value="">
              <em>None</em>
            </MenuItem>
            {items.map(item => (
              <MenuItem key={item.id} index={item.id} value={item.id}>
                {item.name}
              </MenuItem>
            ))}
          </MaybeSelect>
        </DialogContent>
        <DialogActions>
          <Button
            disabled={loading}
            onClick={onClose}
            color="primary"
          >
            Cancel
          </Button>
          <Button
            disabled={loading}
            variant="contained"
            onClick={onClose}
            color="primary"
          >
            Select
          </Button>
        </DialogActions>
      </Dialog>
    </Fragment>
  );
}

这是对话框首次打开时的样子:

图片

对话框显示一个 LinearProgress 组件,并在 API 数据加载时禁用对话框操作按钮。一旦响应到达,对话框看起来是这样的:

图片

线性进度条消失了,对话框操作按钮被启用,并且有一个可供用户选择项目的“选择项”字段可见。以下是显示从 API 加载的项目选择项的“选择项”:

图片

工作原理...

让我们逐步分析这段代码的主要部分,从模拟的 API 函数开始:

const fetchItems = () =>
  new Promise(resolve => {
    setTimeout(() => {
      resolve([
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' }
      ]);
    }, 3000);
  });

fetchItems() 函数通过返回一个在三个秒后解析为数组数据的承诺来模拟一个 API 函数。这允许你在等待实际的 API 端点响应时看到用户将看到的内容。接下来,让我们看看两个帮助渲染或隐藏选择和进度指示器的实用组件:

const MaybeLinearProgress = ({ loading, ...props }) =>
  loading ? <LinearProgress {...props} /> : null;

const MaybeSelect = ({ loading, ...props }) =>
  loading ? null : <Select {...props} />;

理念是,你不想在加载为 false 时渲染 LinearProgress 组件。相反,你不想在加载为 true 时渲染 Select 组件。接下来,让我们看看 onShowItems()

const onShowItems = () => {
  setOpen(true);
  setLoading(true);

  fetchItems().then(items => {
    setLoading(false);
    setItems(items);
  });
};

首先,通过将 open 设置为 true 来打开对话框,并通过将 loading 设置为 true 来显示进度指示器。然后,调用 API 的 fetchItems() 函数,当它返回的 Promise 解析时,将 loading 设置为 false 并更新 items 数组。这隐藏了进度指示器并显示了现在已填充了项目的选择框。

相关内容

创建全屏对话框

在全屏对话框中,您有更多空间来渲染信息。大多数情况下,您不需要全屏对话框。在不常见的情况下,您的对话框需要尽可能多的空间来渲染信息。

如何做到...

假设,在您的应用程序的某个屏幕上有一个按钮,用于导出用户数据。当点击时,您想在用户确认之前给他们一个即将导出的数据的预览。以下是代码的样子:

import React, { Fragment, useState } from 'react';

import { makeStyles } from '@material-ui/styles';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import IconButton from '@material-ui/core/IconButton';
import Typography from '@material-ui/core/Typography';
import Slide from '@material-ui/core/Slide';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';

import CloseIcon from '@material-ui/icons/Close';

const useStyles = makeStyles(theme => ({
  appBar: {
    position: 'relative'
  },
  flex: {
    flex: 1
  }
}));

const Transition = props => <Slide direction="up" {...props} />;

const id = (function*() {
  let id = 0;
  while (true) {
    id += 1;
    yield id;
  }
})();

const rowData = (name, calories, fat, carbs, protein) => ({
  id: id.next().value,
  name,
  calories,
  fat,
  carbs,
  protein
});

const rows = [
  rowData('Frozen yoghurt', 159, 6.0, 24, 4.0),
  rowData('Ice cream sandwich', 237, 9.0, 37, 4.3),
  rowData('Eclair', 262, 16.0, 24, 6.0),
  rowData('Cupcake', 305, 3.7, 67, 4.3),
  rowData('Gingerbread', 356, 16.0, 49, 3.9)
];

export default function FullScreenDialogs() {
  const classes = useStyles();
  const [open, setOpen] = useState(false);

  const onOpen = () => {
    setOpen(true);
  };

  const onClose = () => {
    setOpen(false);
  };

  return (
    <Fragment>
      <Button variant="outlined" color="primary" onClick={onOpen}>
        Export Data
      </Button>
      <Dialog
        fullScreen
        open={open}
        onClose={onClose}
        TransitionComponent={Transition}
      >
        <AppBar className={classes.appBar}>
          <Toolbar>
            <IconButton
              color="inherit"
              onClick={onClose}
              aria-label="Close"
            >
              <CloseIcon />
            </IconButton>
            <Typography
              variant="h6"
              color="inherit"
              className={classes.flex}
            >
              Export Data
            </Typography>
            <Button color="inherit" onClick={onClose}>
              Export
            </Button>
          </Toolbar>
        </AppBar>
        <Table className={classes.table}>
          <TableHead>
            <TableRow>
              <TableCell>Dessert (100g serving)</TableCell>
              <TableCell align="right">Calories</TableCell>
              <TableCell align="right">Fat (g)</TableCell>
              <TableCell align="right">Carbs (g)</TableCell>
              <TableCell align="right">Protein (g)</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {rows.map(row => (
              <TableRow key={row.id}>
                <TableCell component="th" scope="row">
                  {row.name}
                </TableCell>
                <TableCell align="right">{row.calories}</TableCell>
                <TableCell align="right">{row.fat}</TableCell>
                <TableCell align="right">{row.carbs}</TableCell>
                <TableCell align="right">{row.protein}</TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </Dialog>
    </Fragment>
  );
}

这里是对话框打开时的样子:

图片

您可以点击对话框标题旁边的 X 按钮,关闭对话框,或者点击右侧的导出按钮。

它是如何工作的...

让我们看看传递给 Dialog 组件的属性:

<Dialog
  fullScreen
  open={open}
  onClose={onClose}
  TransitionComponent={Transition}
>

fullScreen 布尔属性决定了对话框在全屏模式下的渲染方式。TransitionComponent 属性改变了对话框过渡到屏幕上的方式。

因为对话框以全屏模式显示,您可能想要更改标题和操作对用户显示的方式,如下例所示。您可以使用 AppBarToolbar 组件而不是使用 DialogTitleDialogAction 组件:

<AppBar className={classes.appBar}>
  <Toolbar>
    <IconButton
      color="inherit"
      onClick={onClose}
      aria-label="Close"
    >
      <CloseIcon />
    </IconButton>
    <Typography
      variant="h6"
      color="inherit"
      className={classes.flex}
    >
      Export Data
    </Typography>
    <Button color="inherit" onClick={onClose}>
      Export
    </Button>
  </Toolbar>
</AppBar>

这使得标题、关闭操作和主要操作对用户更加可见。

相关内容

滚动对话框内容

可能很难找到足够的空间将所有内容放入对话框中。当对话框空间不足时,会添加一个垂直滚动条。

如何操作...

假设你有一个需要显示在对话框中供用户在导出为其他格式之前查看的长数据表格。用户将需要能够滚动浏览表格行。以下是一个示例:

import React, { Fragment, useState } from 'react';

import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogActions from '@material-ui/core/DialogActions';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';

const id = (function*() {
  let id = 0;
  while (true) {
    id += 1;
    yield id;
  }
})();

const rowData = (name, calories, fat, carbs, protein) => ({
  id: id.next().value,
  name,
  calories,
  fat,
  carbs,
  protein
});

const rows = new Array(50)
  .fill(null)
  .reduce(
    result =>
      result.concat([
        rowData('Frozen yoghurt', 159, 6.0, 24, 4.0),
        rowData('Ice cream sandwich', 237, 9.0, 37, 4.3),
        rowData('Eclair', 262, 16.0, 24, 6.0),
        rowData('Cupcake', 305, 3.7, 67, 4.3),
        rowData('Gingerbread', 356, 16.0, 49, 3.9)
      ]),
    []
  );

export default function FullScreenDialogs() {
  const [open, setOpen] = useState(false);

  const onOpen = () => {
    setOpen(true);
  };

  const onClose = () => {
    setOpen(false);
  };

  return (
    <Fragment>
      <Button variant="outlined" color="primary" onClick={onOpen}>
        Export Data
      </Button>
      <Dialog open={open} onClose={onClose}>
        <DialogTitle>Desserts</DialogTitle>
        <DialogContent>
          <Table>
            <TableHead>
              <TableRow>
                <TableCell>Dessert (100g serving)</TableCell>
                <TableCell align="right">Calories</TableCell>
                <TableCell align="right">Fat (g)</TableCell>
                <TableCell align="right">Carbs (g)</TableCell>
                <TableCell align="right">Protein (g)</TableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              {rows.map(row => (
                <TableRow key={row.id}>
                  <TableCell component="th" scope="row">
                    {row.name}
                  </TableCell>
                  <TableCell align="right">{row.calories}</TableCell>
                  <TableCell align="right">{row.fat}</TableCell>
                  <TableCell align="right">{row.carbs}</TableCell>
                  <TableCell align="right">{row.protein}</TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        </DialogContent>
        <DialogActions>
          <Button onClick={onClose} color="primary">
            Cancel
          </Button>
          <Button
            variant="contained"
            onClick={onClose}
            color="primary"
          >
            Export
          </Button>
        </DialogActions>
      </Dialog>
    </Fragment>
  );
}

这是对话框打开时的样子:

图片

如果你将鼠标指针移到表格行上并开始滚动,表格行将在对话框标题和对话框操作按钮之间上下滚动。

它是如何工作的...

默认情况下,对话框内容将在对话框的Paper组件(即DialogContent组件)内部滚动,因此无需指定属性。但是,你可以将body值传递给Dialog组件的scroll属性。这将使对话框的高度改变以适应内容。

相关内容