Получение дубликатов при увеличении и уменьшении с помощью клавиш со стрелками с React JS - PullRequest
0 голосов
/ 31 января 2019

Я вернулся к этой проблеме, пытаясь увеличить / уменьшить значение через меню и вот-вот загнать голову, хотя стена это выяснила.Я чувствую, что почти там, но что-то упустил.Я сталкиваюсь с тем, что не могу стрелять вправо, пока не нажму кнопку, а кнопка, которую я нажал, не удалит класс.Любая помощь будет высоко ценится.

Кнопка JS:

...

class Button extends Component {
  onClick() {
    const { label, onClick } = this.props;
    onClick(label);
  }

  render() {
    const {
      onClick,
      props: { activeTab, label, tab, className }
    } = this;
    let ariaSelected = "";
    if (activeTab === label || className === "active") {
      ariaSelected += "true";
    }
    return (
      <li role="presentation">
        <a
          className={className}
          aria-selected={ariaSelected ? "true" : undefined} 
          onClick={e => this.onClick(e)}
          role="tab"
          id={"tab" + tab}
          //tabIndex="-1"
        >
          {label}
        </a>
      </li>
    );
  }
}

..

Меню JS:

class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {
      activeTab: this.props.children[0].props.label,
      cursor: 0
    };
    this.handleKeyDown = this.handleKeyDown.bind(this);
  }

  componentDidMount() {
    document.addEventListener("keydown", this.handleKeyDown, false);
  }

  componentWillUnmount() {
    document.removeEventListener("keydown", this.handleKeyDown, false);
  }

  handleKeyDown(e) {
    const { cursor } = this.state;
    const cnt = React.Children.count(this.props.children);
    if (e.keyCode === 37 && cursor > 0) {
      this.setState(prevState => ({
        cursor: prevState.cursor - 1
      }));
      console.log(cursor);
    } else if (e.keyCode === 39 && cursor < cnt - 1) {
      this.setState(prevState => ({
        cursor: prevState.cursor + 1
      }));
      console.log(cursor);
    }
  }

  onClickTabItem = tab => {
    this.setState({
      activeTab: tab
    });
  };

  render() {
    const {
      onClickTabItem,
      props: { children },
      state: { activeTab, cursor, className }
    } = this;

    return (
      <div className="tabbed">
        <ul role="tablist">
          {children.map((child, i) => {
            const { label, className } = child.props;
            return (
              <Tab
                activeTab={activeTab}
                key={label}
                label={label}
                onClick={onClickTabItem}
                tab={i}
                className={ cursor === i || activeTab === label ? "active" : null}
              />
            );
          })}
        </ul>
        <div className="tab-content">
          {children.map(child => {
            //if tab has label or active set, otherwise do nohthing
            if (child.props.label !== activeTab) return undefined;
            return child.props.children;
          })}
        </div>
      </div>
    );
  }
}

Ответы [ 2 ]

0 голосов
/ 31 января 2019

Выше приведено довольно много повторяющегося и ненужного кода.Я потратил некоторое время на рефакторинг и сделал его немного более модульным / более простым в использовании.

Рабочий пример: https://codesandbox.io/s/rlwq35oz4o

Изменения:

  • Tabs действует как контейнер для нескольких Tab компонентов
  • Tab - это простой многократно используемый компонент, требующий title и children
  • Все вкладки теперь управляются activeTabи сравнивается с сопоставленным key (это можно легко изменить / реализовать для уникального id)
  • Использование prevProps.children.length для определения длины табуляции в обратном вызове setState
  • a (элемент ссылки) TabPane изменен на b (неформатированный текстовый элемент), так как это вызвало проблему стилевого оформления со ссылками, встроенными в TabBody с использованием того же role="tablist"
  • Добавленоword-wrap: break-word; до a элементов в TabBody, чтобы они не сломали section для маленьких экранов.

index.js

import React from "react";
import { render } from "react-dom";
import Tabs, { Tab } from "./components/Tabs";
import "./styles.css";

const App = () => (
  <Tabs>
    <Tab title="Section 1">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod,
      tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim
      lacus a velit.<a href="#">Nam luctus</a>, enim in interdum condimentum,
      nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non
      odio bibendum magna bibendum accumsan.
    </Tab>
    <Tab title="Section 2">
      Nullam at diam nec arcu suscipit auctor non a erat. Sed et magna semper,
      eleifend magna non, facilisis nisl. Proin et est et lorem dictum finibus
      ut nec turpis. Aenean nisi tortor, euismod a mauris a, mattis scelerisque
      tortor. Sed dolor risus, varius a nibh id, condimentum lacinia est. In
      lacinia cursus odio a aliquam. Curabitur tortor magna, laoreet ut rhoncus
      at, sodales consequat
    </Tab>
    <Tab title="Section 3">
      Phasellus ac tristique orci. Nulla maximus
      <a href="">justo nec dignissim consequat</a>. Sed vehicula diam sit amet
      mi efficitur vehicula in in nisl. Aliquam erat volutpat. Suspendisse lorem
      turpis, accumsan consequat consectetur gravida,
      <a href="#">pellentesque ac ante</a>. Aliquam in commodo ligula, sit amet
      mollis neque. Vestibulum at facilisis massa.
    </Tab>
    <Tab title="Section 4">
      Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel
      volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum
      accumsan. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam
      euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec
      laoreet enim lacus a velit.
    </Tab>
  </Tabs>
);

render(<App />, document.getElementById("root"));

компонентов / Tabs.js

import React, { Component } from "react";
import PropTypes from "prop-types";
import TabPane from "./TabPane";
import TabBody from "./TabBody";

export const Tab = ({ title, children }) => <div tab={title}>{children}</div>;

Tab.propTypes = {
  children: PropTypes.node.isRequired,
  title: PropTypes.string.isRequired
};

class Tabs extends Component {
  state = { activeTab: 0 };

  componentDidMount = () => {
    document.addEventListener("keydown", this.handleKeyPress, false);
    window.focus();
  }

  componentWillUnmount = () =>
    document.removeEventListener("keydown", this.handleKeyPress, false);

  handleClickTabItem = ({ target: { id } }) =>
    this.setState({ activeTab: ~~id });

  handleKeyPress = ({ keyCode }) => {
    if (keyCode === 37 || keyCode === 39) {
      this.setState((prevState, prevProps) => {
        const nextTab = keyCode === 37 
          ? prevState.activeTab - 1 
          : prevState.activeTab + 1;

        return nextTab >= 0 && nextTab < prevProps.children.length
          ? { activeTab: nextTab }
          : null;
      });
    }
  };

  render = () => {
    const { activeTab } = this.state;
    const { children } = this.props;
    return (
      <div className="tabbed">
        <ul role="tablist">
          {children.map(({ props }, key) => (
            <TabPane
              key={key}
              activeTab={activeTab}
              onClickTabItem={this.handleClickTabItem}
              id={key}
              {...props}
            />
          ))}
          <div className="tab-content">
            {children.map(({ props }, key) =>
              key === activeTab ? (
                <TabBody key={key} id={key} {...props} />
              ) : null
            )}
          </div>
        </ul>
      </div>
    );
  };
}

Tabs.propTypes = {
  children: PropTypes.node.isRequired
};

export default Tabs;

компонентов / TabPane.js

import React from "react";
import PropTypes from "prop-types";

const TabPane = ({ activeTab, id, onClickTabItem, title, ...rest }) => (
  <li role="presentation">
    <b
      id={id}
      aria-selected={activeTab === id ? "true" : null}
      onClick={onClickTabItem}
      role="tab"
      {...rest}
    >
      {title}
    </b>
  </li>
);

TabPane.propTypes = {
  activeTab: PropTypes.number.isRequired,
  id: PropTypes.number.isRequired,
  onClickTabItem: PropTypes.func.isRequired,
  title: PropTypes.string.isRequired
};

export default TabPane;

компоненты / TabBody.js

import React from "react";
import PropTypes from "prop-types";

const TabBody = ({ title, id, children }) => (
  <section id={id} role="tabpanel" tabIndex="-1" aria-labelledby={id}>
    <h2>{title}</h2>
    <div>{children}</div>
  </section>
);

TabBody.propTypes = {
  children: PropTypes.node.isRequired,
  id: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired
};

export default TabBody;

styles.css

body {
  max-width: 40rem;
  padding: 0 1rem;
  font-size: 125%;
  line-height: 1.5;
  margin: 1.5rem auto;
  font-family: "Lato", Arial, sans-serif;
  font-size: 16px;
}

* {
  color: inherit;
  margin: 0;
}

[role="tablist"] {
  padding: 0;
}

[role="tablist"] li,
[role="tablist"] b {
  display: inline-block;
}

[role="tablist"] b {
  text-decoration: none;
  padding: 0.5rem 1em;
  cursor: pointer;
}

[role="tablist"] a {
  text-decoration: none;
  padding-left: 0.2rem;
  word-wrap: break-word;
}

[role="tablist"] [aria-selected] {
  border: 2px solid;
  background: #fff;
  border-bottom: 0;
  position: relative;
  top: 2px;
}

[role="tabpanel"] {
  border: 2px solid;
  padding: 1.5rem;
}

[role="tabpanel"] * + * {
  margin-top: 0.75rem;
}

*:focus {
  outline: none;
  box-shadow: inset 0 0 0 4px lightBlue;
}

@media (max-width: 550px) {
  [role="tablist"] li,
  [role="tablist"] b {
    display: block;
    position: static;
  }

  [role="tablist"] b {
    border: 2px solid #222 !important;
  }

  [role="tablist"] li + li b {
    border-top: 0 !important;
  }

  [role="tablist"] [aria-selected] {
    position: static;
  }

  [role="tablist"] [aria-selected]::after {
    content: "\0020⬅";
  }

  [role="tabpanel"] {
    border-top: 0;
  }
}

section a {
  color: rgb(66, 133, 244);
}
0 голосов
/ 31 января 2019

Вы можете получить номер метки из activeTab вместо переменной курсора, когда нажимаются клавиши со стрелками:

  handleKeyDown(e) {
    const cnt = React.Children.count(this.props.children);
    const pos = ~~this.state.activeTab[8]  // get current position
    if (e.keyCode === 37 && pos > 1) {
      this.setState({
        activeTab: "Section " + (pos - 1)
      });
    } else if (e.keyCode === 39 && pos < cnt) {
      this.setState({
        activeTab: "Section " + (pos + 1)
      });
    }
  }

, а затем, когда для className установлено значение, измените cursor === i || activeTab === label ? "active" : null на activeTab === label ? "active" : null

edit: Этот способ немного чище, если вы планируете изменить заголовки разделов, что, я полагаю, вы могли бы.

  handleKeyDown(e) {
    const labels = this.props.children.map((child) => {
      return child.props.label;
    });
    const cnt = labels.length;
    const pos = labels.indexOf(this.state.activeTab); // get current position
    if (e.keyCode === 37 && pos > 0) {
      this.setState({
        activeTab: labels[pos - 1]
      });
    } else if (e.keyCode === 39 && pos < cnt - 1) {
      this.setState({
        activeTab: labels[pos + 1]
      });
    }
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...