mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-22 12:24:02 +03:00
feat(FocusRing): add FocusRing component
This commit is contained in:
parent
c4c7fec101
commit
5a1c13700d
@ -103,7 +103,7 @@ const STYLES_UPLOAD_BUTTON = css`
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
transition: all 200ms;
|
||||
transition: background-color 200ms;
|
||||
|
||||
:hover {
|
||||
background-color: ${Constants.semantic.bgGrayLight4};
|
||||
|
56
components/core/FocusRing.js
Normal file
56
components/core/FocusRing.js
Normal file
@ -0,0 +1,56 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { mergeEvents, cloneElementWithJsx } from "~/common/utilities";
|
||||
import { useCombinedRefs } from "~/common/hooks";
|
||||
|
||||
const hasElementFocus = (el) => el.matches(":focus");
|
||||
const hasElementFocusWithin = (el) => el.matches(":focus-within");
|
||||
|
||||
let isFocusViaKeyboard = false;
|
||||
if (typeof window !== "undefined") {
|
||||
window?.addEventListener("mousedown", () => (isFocusViaKeyboard = false));
|
||||
window?.addEventListener("keydown", () => (isFocusViaKeyboard = true));
|
||||
}
|
||||
|
||||
const useFocusRing = ({ within, ref }) => {
|
||||
const [isFocused, setFocus] = React.useState(false);
|
||||
|
||||
const enableFocusRing = () => {
|
||||
if (isFocusViaKeyboard && ref.current) {
|
||||
setFocus(within ? hasElementFocusWithin(ref.current) : hasElementFocus(ref.current));
|
||||
}
|
||||
};
|
||||
const disableFocusRing = () => setFocus(false);
|
||||
|
||||
return [isFocused, { enableFocusRing, disableFocusRing }];
|
||||
};
|
||||
|
||||
const STYLES_FOCUS_RING_DEFAULT = (theme) => css`
|
||||
outline: 2px solid ${theme.system.blue};
|
||||
`;
|
||||
|
||||
export const FocusRing = React.forwardRef(
|
||||
(
|
||||
{ children, within, style, css = STYLES_FOCUS_RING_DEFAULT, disabled, ...props },
|
||||
forwardedRef
|
||||
) => {
|
||||
const innerRef = React.useRef();
|
||||
const ref = useCombinedRefs(innerRef, forwardedRef, children.ref);
|
||||
|
||||
const [isFocused, { enableFocusRing, disableFocusRing }] = useFocusRing({ within, ref });
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
enableFocusRing();
|
||||
}, []);
|
||||
|
||||
return cloneElementWithJsx(React.Children.only(children), {
|
||||
...props,
|
||||
onFocus: mergeEvents(enableFocusRing, children.onFocus),
|
||||
onBlur: mergeEvents(disableFocusRing, children.onBlur),
|
||||
css: isFocused && !disabled ? css : null,
|
||||
style: isFocused && !disabled ? style : null,
|
||||
ref,
|
||||
});
|
||||
}
|
||||
);
|
@ -19,7 +19,6 @@ const STYLES_CODE_TEXTAREA = css`
|
||||
resize: none;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
scrollbar-width: none;
|
||||
|
@ -4,8 +4,8 @@ import * as SVG from "~/common/svg";
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
import { DescriptionGroup } from "~/components/system/components/fragments/DescriptionGroup";
|
||||
import { FocusRing } from "~/components/core/FocusRing";
|
||||
|
||||
const INPUT_STYLES = css`
|
||||
box-sizing: border-box;
|
||||
@ -22,10 +22,6 @@ const INPUT_STYLES = css`
|
||||
box-sizing: border-box;
|
||||
transition: 200ms ease all;
|
||||
|
||||
:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: ${Constants.system.grayLight2};
|
||||
@ -180,9 +176,9 @@ export class Input extends React.Component {
|
||||
this.props.onSubmit(e);
|
||||
};
|
||||
|
||||
_handleKeyUp = (e) => {
|
||||
if (this.props.onKeyUp) {
|
||||
this.props.onKeyUp(e);
|
||||
_handleKeyDown = (e) => {
|
||||
if (this.props.onKeyDown) {
|
||||
this.props.onKeyDown(e);
|
||||
}
|
||||
|
||||
if ((e.which === 13 || e.keyCode === 13) && this.props.onSubmit) {
|
||||
@ -232,72 +228,74 @@ export class Input extends React.Component {
|
||||
style={this.props.descriptionStyle}
|
||||
description={this.props.description}
|
||||
/>
|
||||
<div
|
||||
css={[STYLES_INPUT, this.props.inputCss]}
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: this.props.validation
|
||||
? `0 1px 4px rgba(0, 0, 0, 0.07), inset 0 0 0 2px ${
|
||||
INPUT_COLOR_MAP[this.props.validation]
|
||||
}`
|
||||
: null,
|
||||
...this.props.style,
|
||||
}}
|
||||
>
|
||||
<input
|
||||
ref={(c) => {
|
||||
this._input = c;
|
||||
}}
|
||||
css={[INPUT_STYLES, this._isPin && STYLES_PIN_INPUT]}
|
||||
autoFocus={this.props.autoFocus}
|
||||
value={this._isPin ? this._formatPin(this.props.value) : this.props.value}
|
||||
name={this.props.name}
|
||||
type={this._isPin ? "text" : this.props.type}
|
||||
placeholder={this.props.placeholder}
|
||||
onChange={this._handleChange}
|
||||
onFocus={
|
||||
this.props.autoHighlight
|
||||
? () => {
|
||||
this._input.select();
|
||||
}
|
||||
: this.props.onFocus
|
||||
}
|
||||
onBlur={this.props.onBlur}
|
||||
onKeyUp={this._handleKeyUp}
|
||||
autoComplete="off"
|
||||
disabled={this.props.disabled}
|
||||
readOnly={this.props.readOnly}
|
||||
required={this.props.required}
|
||||
maxLength={this.props.maxLength}
|
||||
style={{
|
||||
width: this.props.copyable || this.props.icon ? "calc(100% - 32px)" : "100%",
|
||||
...this.props.textStyle,
|
||||
}}
|
||||
/>
|
||||
<FocusRing within>
|
||||
<div
|
||||
css={STYLES_UNIT}
|
||||
ref={(c) => {
|
||||
this._unit = c;
|
||||
css={[STYLES_INPUT, this.props.inputCss]}
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: this.props.validation
|
||||
? `0 1px 4px rgba(0, 0, 0, 0.07), inset 0 0 0 2px ${
|
||||
INPUT_COLOR_MAP[this.props.validation]
|
||||
}`
|
||||
: null,
|
||||
...this.props.style,
|
||||
}}
|
||||
>
|
||||
{this.props.unit}
|
||||
<input
|
||||
ref={(c) => {
|
||||
this._input = c;
|
||||
}}
|
||||
css={[INPUT_STYLES, this._isPin && STYLES_PIN_INPUT]}
|
||||
autoFocus={this.props.autoFocus}
|
||||
value={this._isPin ? this._formatPin(this.props.value) : this.props.value}
|
||||
name={this.props.name}
|
||||
type={this._isPin ? "text" : this.props.type}
|
||||
placeholder={this.props.placeholder}
|
||||
onChange={this._handleChange}
|
||||
onFocus={
|
||||
this.props.autoHighlight
|
||||
? () => {
|
||||
this._input.select();
|
||||
}
|
||||
: this.props.onFocus
|
||||
}
|
||||
onBlur={this.props.onBlur}
|
||||
onKeyDown={this._handleKeyDown}
|
||||
autoComplete="off"
|
||||
disabled={this.props.disabled}
|
||||
readOnly={this.props.readOnly}
|
||||
required={this.props.required}
|
||||
maxLength={this.props.maxLength}
|
||||
style={{
|
||||
width: this.props.copyable || this.props.icon ? "calc(100% - 32px)" : "100%",
|
||||
...this.props.textStyle,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
css={STYLES_UNIT}
|
||||
ref={(c) => {
|
||||
this._unit = c;
|
||||
}}
|
||||
>
|
||||
{this.props.unit}
|
||||
</div>
|
||||
{this.props.unit ? null : this.props.icon ? (
|
||||
<this.props.icon
|
||||
height="16px"
|
||||
css={STYLES_ICON}
|
||||
style={{ cursor: (this.props.onClickIcon || this.props.onSubmit) && "pointer" }}
|
||||
onClick={this.props.onClickIcon || this.props.onSubmit || this._handleSubmit}
|
||||
/>
|
||||
) : this.props.copyable ? (
|
||||
<SVG.CopyAndPaste
|
||||
height="16px"
|
||||
css={STYLES_ICON}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={this._handleCopy}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{this.props.unit ? null : this.props.icon ? (
|
||||
<this.props.icon
|
||||
height="16px"
|
||||
css={STYLES_ICON}
|
||||
style={{ cursor: (this.props.onClickIcon || this.props.onSubmit) && "pointer" }}
|
||||
onClick={this.props.onClickIcon || this.props.onSubmit || this._handleSubmit}
|
||||
/>
|
||||
) : this.props.copyable ? (
|
||||
<SVG.CopyAndPaste
|
||||
height="16px"
|
||||
css={STYLES_ICON}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={this._handleCopy}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</FocusRing>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -20,9 +20,7 @@ const STYLES_TEXTAREA = css`
|
||||
font-size: 16px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
transition: 200ms ease all;
|
||||
padding: 9px 12px 11px;
|
||||
box-shadow: 0 0 0 1px ${Constants.semantic.borderGrayLight} inset;
|
||||
|
||||
|
35
vendor/react-textarea-autosize.js
vendored
35
vendor/react-textarea-autosize.js
vendored
@ -1,11 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import calculateNodeHeight, {
|
||||
purgeCache,
|
||||
} from "~/vendor/calculate-node-height";
|
||||
import calculateNodeHeight, { purgeCache } from "~/vendor/calculate-node-height";
|
||||
import { FocusRing } from "~/components/core/FocusRing";
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
const STYLES_TEXTAREA = css`
|
||||
outline: 0;
|
||||
`;
|
||||
|
||||
let uid = 0;
|
||||
|
||||
export default class TextareaAutosize extends React.Component {
|
||||
@ -36,21 +40,28 @@ export default class TextareaAutosize extends React.Component {
|
||||
minRows,
|
||||
onHeightChange,
|
||||
useCacheForDOMMeasurements,
|
||||
css,
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
props.style = { ...props.style, height: this.state.height };
|
||||
|
||||
const maxHeight = Math.max(
|
||||
props.style.maxHeight || Infinity,
|
||||
this.state.maxHeight
|
||||
);
|
||||
const maxHeight = Math.max(props.style.maxHeight || Infinity, this.state.maxHeight);
|
||||
|
||||
if (maxHeight < this.state.height) {
|
||||
props.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
return <textarea {...props} onChange={this._onChange} ref={this._onRef} />;
|
||||
return (
|
||||
<FocusRing>
|
||||
<textarea
|
||||
{...props}
|
||||
css={[STYLES_TEXTAREA, css]}
|
||||
onChange={this._onChange}
|
||||
ref={this._onRef}
|
||||
/>
|
||||
</FocusRing>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -123,13 +134,7 @@ export default class TextareaAutosize extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
height,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
rowCount,
|
||||
valueRowCount,
|
||||
} = nodeHeight;
|
||||
const { height, minHeight, maxHeight, rowCount, valueRowCount } = nodeHeight;
|
||||
|
||||
this.rowCount = rowCount;
|
||||
this.valueRowCount = valueRowCount;
|
||||
|
Loading…
Reference in New Issue
Block a user