feat(FocusRing): add FocusRing component

This commit is contained in:
Aminejv 2022-01-02 16:28:44 +01:00
parent c4c7fec101
commit 5a1c13700d
6 changed files with 145 additions and 89 deletions

View File

@ -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};

View 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,
});
}
);

View File

@ -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;

View File

@ -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,6 +228,7 @@ export class Input extends React.Component {
style={this.props.descriptionStyle}
description={this.props.description}
/>
<FocusRing within>
<div
css={[STYLES_INPUT, this.props.inputCss]}
style={{
@ -263,7 +260,7 @@ export class Input extends React.Component {
: this.props.onFocus
}
onBlur={this.props.onBlur}
onKeyUp={this._handleKeyUp}
onKeyDown={this._handleKeyDown}
autoComplete="off"
disabled={this.props.disabled}
readOnly={this.props.readOnly}
@ -298,6 +295,7 @@ export class Input extends React.Component {
/>
) : null}
</div>
</FocusRing>
</div>
</>
);

View File

@ -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;

View File

@ -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;