Skip to content

Commit

Permalink
components: Add BaseField (#32250)
Browse files Browse the repository at this point in the history
* components: Add `BaseField`

* Give better tests and fix types

* Remove unnecessary eslint disable

* Remove isFocused prop

* Rename error to include a verb

* Fix admin colors

* Remove unused manual focus styles

* Fix README for removed/updated props

* Remove component and update tests

* Update packages/components/src/base-field/hook.js

Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>

* Fully remove isClickable

Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>
  • Loading branch information
sarayourfriend and ciampo authored May 28, 2021
1 parent af89254 commit 8f29773
Show file tree
Hide file tree
Showing 10 changed files with 479 additions and 0 deletions.
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,12 @@
"markdown_source": "../packages/components/src/base-control/README.md",
"parent": "components"
},
{
"title": "BaseField",
"slug": "base-field",
"markdown_source": "../packages/components/src/base-field/README.md",
"parent": "components"
},
{
"title": "BoxControl",
"slug": "box-control",
Expand Down
67 changes: 67 additions & 0 deletions packages/components/src/base-field/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# BaseField

<div class="callout callout-alert">
This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
</div>

`BaseField` is an internal (i.e., not exported in the `index.js`) primitive component used for building more complex fields like `TextField`. It provides error handling and focus styles for field components. It does _not_ handle layout of the component aside from wrapping the field in a `Flex` wrapper.

## Usage

`BaseField` is primarily used as a hook rather than a component:

```js
function useExampleField( props ) {
const {
as = 'input',
...baseProps,
} = useBaseField( props );

const inputProps = {
as,
// more cool stuff here
}

return { inputProps, ...baseProps };
}

function ExampleField( props, forwardRef ) {
const {
preFix,
affix,
disabled,
inputProps,
...baseProps
} = useExampleField( props );

return (
<View { ...baseProps } disabled={ disabled }>
{preFix}
<View
autocomplete="off"
{ ...inputProps }
disabled={ disabled }
/>
{affix}
</View>
);
}
```

## Props

### `hasError`: `boolean`

Renders an error style around the component.

### `disabled`: `boolean`

Whether the field is disabled.

### `isInline`: `boolean`

Renders a component that can be inlined in some text.

### `isSubtle`: `boolean`

Renders a subtle variant of the component.
64 changes: 64 additions & 0 deletions packages/components/src/base-field/hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* External dependencies
*/
import { cx } from 'emotion';

/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import { useContextSystem } from '../ui/context';
import { useControlGroupContext } from '../ui/control-group';
import { useFlex } from '../flex';
import * as styles from './styles';

/**
* @typedef OwnProps
* @property {boolean} [hasError=false] Renders an error.
* @property {boolean} [disabled] Whether the field is disabled.
* @property {boolean} [isInline=false] Renders as an inline element (layout).
* @property {boolean} [isSubtle=false] Renders a subtle variant.
*/

/** @typedef {import('../flex/types').FlexProps & OwnProps} Props */

/**
* @param {import('../ui/context').PolymorphicComponentProps<Props, 'div'>} props
*/
export function useBaseField( props ) {
const {
className,
hasError = false,
isInline = false,
isSubtle = false,
// extract these because useFlex doesn't accept it
defaultValue,
disabled,
...flexProps
} = useContextSystem( props, 'BaseField' );

const { styles: controlGroupStyles } = useControlGroupContext();

const classes = useMemo(
() =>
cx(
styles.BaseField,
controlGroupStyles,
isSubtle && styles.subtle,
hasError && styles.error,
isInline && styles.inline,
className
),
[ className, controlGroupStyles, hasError, isInline, isSubtle ]
);

return {
...useFlex( { ...flexProps, className: classes } ),
disabled,
defaultValue,
};
}
1 change: 1 addition & 0 deletions packages/components/src/base-field/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useBaseField } from './hook';
86 changes: 86 additions & 0 deletions packages/components/src/base-field/styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* External dependencies
*/
import { css } from 'emotion';

/**
* Internal dependencies
*/
import { CONFIG, COLORS, reduceMotion } from '../utils';
import { safariOnly } from '../utils/browsers';

export const BaseField = css`
background: ${ CONFIG.controlBackgroundColor };
border-radius: ${ CONFIG.controlBorderRadius };
border: 1px solid;
border-color: ${ CONFIG.controlBorderColor };
box-shadow: ${ CONFIG.controlBoxShadow };
display: flex;
flex: 1;
font-size: ${ CONFIG.fontSize };
outline: none;
padding: 0 8px;
position: relative;
transition: border-color ${ CONFIG.transitionDurationFastest } ease;
${ reduceMotion( 'transition' ) }
width: 100%;
&[disabled] {
opacity: 0.6;
}
&:hover {
border-color: ${ CONFIG.controlBorderColorHover };
}
&:focus,
&[data-focused='true'] {
border-color: ${ COLORS.admin.theme };
box-shadow: ${ CONFIG.controlBoxShadowFocus };
}
`;

export const subtle = css`
background-color: transparent;
&:hover,
&:active,
&:focus,
&[data-focused='true'] {
background: ${ CONFIG.controlBackgroundColor };
}
`;

export const error = css`
border-color: ${ CONFIG.controlDestructiveBorderColor };
&:hover,
&:active {
border-color: ${ CONFIG.controlDestructiveBorderColor };
}
&:focus,
&[data-focused='true'] {
border-color: ${ CONFIG.controlDestructiveBorderColor };
box-shadow: 0 0 0, 0.5px, ${ CONFIG.controlDestructiveBorderColor };
}
`;

export const errorFocus = css`
border-color: ${ CONFIG.controlDestructiveBorderColor };
box-shadow: 0 0 0, 0.5px, ${ CONFIG.controlDestructiveBorderColor };
&:hover {
border-color: ${ CONFIG.controlDestructiveBorderColor };
}
`;

export const inline = css`
display: inline-flex;
vertical-align: baseline;
width: auto;
${ safariOnly`
vertical-align: middle;
` }
`;
143 changes: 143 additions & 0 deletions packages/components/src/base-field/test/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`base field props should render error styles 1`] = `
Snapshot Diff:
- Received styles
+ Base styles
@@ -12,11 +12,11 @@
"-webkit-justify-content": "space-between",
"-webkit-transition": "border-color 100ms ease",
"align-items": "center",
"background": "#fff",
"border": "1px solid",
- "border-color": "#d94f4f",
+ "border-color": "#757575",
"border-radius": "2px",
"box-shadow": "transparent",
"display": "flex",
"flex": "1",
"flex-direction": "row",
`;

exports[`base field props should render inline styles 1`] = `
Snapshot Diff:
- Received styles
+ Base styles
@@ -15,18 +15,17 @@
"background": "#fff",
"border": "1px solid",
"border-color": "#757575",
"border-radius": "2px",
"box-shadow": "transparent",
- "display": "inline-flex",
+ "display": "flex",
"flex": "1",
"flex-direction": "row",
"font-size": "13px",
"justify-content": "space-between",
"outline": "none",
"padding": "0 8px",
"position": "relative",
"transition": "border-color 100ms ease",
- "vertical-align": "baseline",
- "width": "auto",
+ "width": "100%",
},
]
`;

exports[`base field props should render subtle styles 1`] = `
Snapshot Diff:
- Received styles
+ Base styles
@@ -11,11 +11,10 @@
"-webkit-flex-direction": "row",
"-webkit-justify-content": "space-between",
"-webkit-transition": "border-color 100ms ease",
"align-items": "center",
"background": "#fff",
- "background-color": "transparent",
"border": "1px solid",
"border-color": "#757575",
"border-radius": "2px",
"box-shadow": "transparent",
"display": "flex",
`;

exports[`base field should render correctly 1`] = `
.emotion-0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
width: 100%;
background: #fff;
border-radius: 2px;
border: 1px solid;
border-color: #757575;
box-shadow: transparent;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
font-size: 13px;
outline: none;
padding: 0 8px;
position: relative;
-webkit-transition: border-color 100ms ease;
transition: border-color 100ms ease;
width: 100%;
}
.emotion-0 > * + *:not(marquee) {
margin-left: calc(4px * 2);
}
.emotion-0 > * {
min-width: 0;
}
@media ( prefers-reduced-motion:reduce ) {
.emotion-0 {
-webkit-transition-duration: 0ms;
transition-duration: 0ms;
}
}
.emotion-0[disabled] {
opacity: 0.6;
}
.emotion-0:hover {
border-color: #757575;
}
.emotion-0:focus,
.emotion-0[data-focused='true'] {
border-color: var( --wp-admin-theme-color,#00669b);
box-shadow: 0 0 0,0.5px,[object Object];
}
<div
class="components-flex components-base-field emotion-0 emotion-1 emotion-2"
data-wp-c16t="true"
data-wp-component="BaseField"
/>
`;
Loading

0 comments on commit 8f29773

Please sign in to comment.