feat: finishes tabs field
This commit is contained in:
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -31,7 +31,7 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"BABEL_ENV": "development"
|
"BABEL_ENV": "development"
|
||||||
},
|
},
|
||||||
"program": "${workspaceFolder}/test/dev/index.js",
|
"program": "${workspaceFolder}/test/dev.js",
|
||||||
"skipFiles": [
|
"skipFiles": [
|
||||||
"<node_internals>/**"
|
"<node_internals>/**"
|
||||||
],
|
],
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"--nolazy"
|
"--nolazy"
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
"e2e/fields"
|
"fields"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import React from 'react';
|
|||||||
import { Props, isComponent } from './types';
|
import { Props, isComponent } from './types';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
|
const baseClass = 'field-description';
|
||||||
|
|
||||||
const FieldDescription: React.FC<Props> = (props) => {
|
const FieldDescription: React.FC<Props> = (props) => {
|
||||||
const {
|
const {
|
||||||
|
className,
|
||||||
description,
|
description,
|
||||||
value,
|
value,
|
||||||
} = props;
|
} = props;
|
||||||
@@ -17,7 +20,10 @@ const FieldDescription: React.FC<Props> = (props) => {
|
|||||||
if (description) {
|
if (description) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="field-description"
|
className={[
|
||||||
|
baseClass,
|
||||||
|
className,
|
||||||
|
].filter(Boolean).join(' ')}
|
||||||
>
|
>
|
||||||
{typeof description === 'function' ? description({ value }) : description}
|
{typeof description === 'function' ? description({ value }) : description}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export type Description = string | DescriptionFunction | DescriptionComponent
|
|||||||
export type Props = {
|
export type Props = {
|
||||||
description?: Description
|
description?: Description
|
||||||
value?: unknown;
|
value?: unknown;
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isComponent(description: Description): description is DescriptionComponent {
|
export function isComponent(description: Description): description is DescriptionComponent {
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
@import '../../../../scss/styles.scss';
|
||||||
|
|
||||||
|
.tabs-field {
|
||||||
|
margin-left: calc(var(--gutter-h) * -1);
|
||||||
|
margin-right: calc(var(--gutter-h) * -1);
|
||||||
|
margin-bottom: base(2);
|
||||||
|
|
||||||
|
&__tabs,
|
||||||
|
&__content-wrap {
|
||||||
|
padding-left: var(--gutter-h);
|
||||||
|
padding-right: var(--gutter-h);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--within-collapsible {
|
||||||
|
margin-left: calc(#{$baseline} * -1);
|
||||||
|
margin-right: calc(#{$baseline} * -1);
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.tabs-field__tabs,
|
||||||
|
.tabs-field__content-wrap {
|
||||||
|
padding-left: $baseline;
|
||||||
|
padding-right: $baseline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tabs {
|
||||||
|
border-bottom: 1px solid var(--theme-elevation-100);
|
||||||
|
margin-bottom: $baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tab-button {
|
||||||
|
@extend %btn-reset;
|
||||||
|
@extend %h4;
|
||||||
|
padding-bottom: base(.5);
|
||||||
|
margin: 0 $baseline 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: .5;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: -1px;
|
||||||
|
left: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--theme-elevation-800);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: .75;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
opacity: .2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tab-button--active {
|
||||||
|
opacity: 1 !important;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
opacity: 1 !important;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
margin-bottom: $baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include small-break {
|
||||||
|
&--within-collapsible {
|
||||||
|
margin-left: calc(var(--gutter-h) * -1);
|
||||||
|
margin-right: calc(var(--gutter-h) * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tab-button {
|
||||||
|
margin: 0 base(.75) 0 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Props } from './types';
|
|||||||
import { fieldAffectsData } from '../../../../../fields/config/types';
|
import { fieldAffectsData } from '../../../../../fields/config/types';
|
||||||
import FieldDescription from '../../FieldDescription';
|
import FieldDescription from '../../FieldDescription';
|
||||||
import toKebabCase from '../../../../../utilities/toKebabCase';
|
import toKebabCase from '../../../../../utilities/toKebabCase';
|
||||||
|
import { useCollapsible } from '../../../elements/Collapsible/provider';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ const TabsField: React.FC<Props> = (props) => {
|
|||||||
},
|
},
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const isWithinCollapsible = useCollapsible();
|
||||||
const [active, setActive] = useState(0);
|
const [active, setActive] = useState(0);
|
||||||
|
|
||||||
const activeTab = tabs[active];
|
const activeTab = tabs[active];
|
||||||
@@ -30,6 +32,7 @@ const TabsField: React.FC<Props> = (props) => {
|
|||||||
<div className={[
|
<div className={[
|
||||||
className,
|
className,
|
||||||
baseClass,
|
baseClass,
|
||||||
|
isWithinCollapsible && `${baseClass}--within-collapsible`,
|
||||||
].filter(Boolean).join(' ')}
|
].filter(Boolean).join(' ')}
|
||||||
>
|
>
|
||||||
<div className={`${baseClass}__tabs`}>
|
<div className={`${baseClass}__tabs`}>
|
||||||
@@ -38,7 +41,10 @@ const TabsField: React.FC<Props> = (props) => {
|
|||||||
<button
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
type="button"
|
type="button"
|
||||||
className={`${baseClass}__tab`}
|
className={[
|
||||||
|
`${baseClass}__tab-button`,
|
||||||
|
active === i && `${baseClass}__tab-button--active`,
|
||||||
|
].filter(Boolean).join(' ')}
|
||||||
onClick={() => setActive(i)}
|
onClick={() => setActive(i)}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
@@ -54,6 +60,7 @@ const TabsField: React.FC<Props> = (props) => {
|
|||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
<FieldDescription
|
<FieldDescription
|
||||||
|
className={`${baseClass}__description`}
|
||||||
description={activeTab.description}
|
description={activeTab.description}
|
||||||
/>
|
/>
|
||||||
<RenderFields
|
<RenderFields
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
margin: 0 0 $baseline;
|
margin: 0 0 $baseline;
|
||||||
font-size: base(.75);
|
font-size: base(.75);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
letter-spacing: -.375px;
|
||||||
}
|
}
|
||||||
|
|
||||||
%h5 {
|
%h5 {
|
||||||
|
|||||||
@@ -304,48 +304,44 @@ const fieldToSchemaMap = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
row: (field: RowField, fields: SchemaDefinition, config: SanitizedConfig, buildSchemaOptions: BuildSchemaOptions): SchemaDefinition => {
|
row: (field: RowField, fields: SchemaDefinition, config: SanitizedConfig, buildSchemaOptions: BuildSchemaOptions): SchemaDefinition => {
|
||||||
const newFields = { ...fields };
|
let newFields = { ...fields };
|
||||||
|
|
||||||
field.fields.forEach((subField: Field) => {
|
field.fields.forEach((subField: Field) => {
|
||||||
const fieldSchemaMap: FieldSchemaGenerator = fieldToSchemaMap[subField.type];
|
const fieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type];
|
||||||
|
|
||||||
if (fieldSchemaMap && fieldAffectsData(subField)) {
|
if (fieldSchema) {
|
||||||
const fieldSchema = fieldSchemaMap(subField, fields, config, buildSchemaOptions);
|
newFields = fieldSchema(subField, newFields, config, buildSchemaOptions);
|
||||||
newFields[subField.name] = fieldSchema[subField.name];
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return newFields;
|
return newFields;
|
||||||
},
|
},
|
||||||
collapsible: (field: CollapsibleField, fields: SchemaDefinition, config: SanitizedConfig, buildSchemaOptions: BuildSchemaOptions): SchemaDefinition => {
|
collapsible: (field: CollapsibleField, fields: SchemaDefinition, config: SanitizedConfig, buildSchemaOptions: BuildSchemaOptions): SchemaDefinition => {
|
||||||
const newFields = { ...fields };
|
let newFields = { ...fields };
|
||||||
|
|
||||||
field.fields.forEach((subField: Field) => {
|
field.fields.forEach((subField: Field) => {
|
||||||
const fieldSchemaMap: FieldSchemaGenerator = fieldToSchemaMap[subField.type];
|
const fieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type];
|
||||||
|
|
||||||
if (fieldSchemaMap && fieldAffectsData(subField)) {
|
if (fieldSchema) {
|
||||||
const fieldSchema = fieldSchemaMap(subField, fields, config, buildSchemaOptions);
|
newFields = fieldSchema(subField, newFields, config, buildSchemaOptions);
|
||||||
newFields[subField.name] = fieldSchema[subField.name];
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return newFields;
|
return newFields;
|
||||||
},
|
},
|
||||||
tabs: (field: TabsField, fields: SchemaDefinition, config: SanitizedConfig, buildSchemaOptions: BuildSchemaOptions): SchemaDefinition => {
|
tabs: (field: TabsField, fields: SchemaDefinition, config: SanitizedConfig, buildSchemaOptions: BuildSchemaOptions): SchemaDefinition => {
|
||||||
const newFields = { ...fields };
|
let newFields = { ...fields };
|
||||||
|
|
||||||
field.tabs.forEach((tab) => {
|
field.tabs.forEach((tab) => {
|
||||||
tab.fields.forEach((subField: Field) => {
|
tab.fields.forEach((subField: Field) => {
|
||||||
const fieldSchemaMap: FieldSchemaGenerator = fieldToSchemaMap[subField.type];
|
const fieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type];
|
||||||
|
|
||||||
if (fieldSchemaMap && fieldAffectsData(subField)) {
|
if (fieldSchema) {
|
||||||
const fieldSchema = fieldSchemaMap(subField, fields, config, buildSchemaOptions);
|
newFields = fieldSchema(subField, newFields, config, buildSchemaOptions);
|
||||||
newFields[subField.name] = fieldSchema[subField.name];
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return newFields;
|
return newFields;
|
||||||
},
|
},
|
||||||
array: (field: ArrayField, fields: SchemaDefinition, config: SanitizedConfig, buildSchemaOptions: BuildSchemaOptions) => {
|
array: (field: ArrayField, fields: SchemaDefinition, config: SanitizedConfig, buildSchemaOptions: BuildSchemaOptions) => {
|
||||||
|
|||||||
@@ -49,6 +49,38 @@ const TabsFields: CollectionConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'collapsible',
|
||||||
|
label: 'Tabs within Collapsible',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'tabs',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
label: 'Nested Tab One',
|
||||||
|
description: 'Here is a description for a nested tab',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'textarea',
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Nested Tab Two',
|
||||||
|
description: 'Description for tab two',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'anotherText',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,6 +98,8 @@ export const tabsDoc = {
|
|||||||
],
|
],
|
||||||
text: 'This text will show up in the second tab input',
|
text: 'This text will show up in the second tab input',
|
||||||
number: 12,
|
number: 12,
|
||||||
|
textarea: 'Here is some text that goes in a textarea',
|
||||||
|
anotherText: 'Super tired of writing this text',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TabsFields;
|
export default TabsFields;
|
||||||
|
|||||||
Reference in New Issue
Block a user