Skip to content

Commit 5548f59

Browse files
[6.x] wrapFieldsInCards mode for forms (#11757)
* wrapFieldsInCards mode for forms * A Flexbox "Grid" for blueprint column sections * A Flexbox "Grid" for blueprint column sections -- neater * refactor flex basis grid, make new fieldtype-width-class helper * ...and make it responsive * fix tests --------- Co-authored-by: Jay George <contact@jaygeorge.co.uk>
1 parent dce37c4 commit 5548f59

14 files changed

Lines changed: 156 additions & 49 deletions

File tree

resources/css/components/publish.css

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,46 @@ code.parent-url {
4141
}
4242

4343
.publish-fields {
44-
@apply flex flex-wrap;
44+
@apply flex flex-wrap gap-3 lg:gap-6;
45+
--col-width: calc(100% / 12);
4546

47+
.form-group {
48+
--col-span: 12;
49+
flex-basis: calc(var(--col-width) * 12 - var(--spacing) * 3);
50+
flex-grow: 1;
51+
}
52+
53+
.field-w-10 { --col-span: 1; }
54+
.field-w-20 { --col-span: 2; }
55+
.field-w-25 { --col-span: 3; }
56+
.field-w-33 { --col-span: 4; }
57+
.field-w-40 { --col-span: 5; }
58+
.field-w-50 { --col-span: 6; }
59+
.field-w-60 { --col-span: 7; }
60+
.field-w-66 { --col-span: 8; }
61+
.field-w-75 { --col-span: 9; }
62+
.field-w-80 { --col-span: 10; }
63+
.field-w-90 { --col-span: 11; }
64+
.field-w-100 { --col-span: 12; }
65+
66+
[data-cards-wrap="fields"] & {
67+
@apply gap-1;
68+
.form-group {
69+
flex-basis: calc(var(--col-width) * 12 - calc(var(--spacing) * 1));
70+
}
71+
}
72+
}
73+
74+
@variant lg {
75+
.publish-fields .form-group {
76+
flex-basis: calc(var(--col-width) * var(--col-span) - calc(var(--spacing) * 3));
77+
}
78+
[data-cards-wrap="fields"] .publish-fields .form-group {
79+
flex-basis: calc(var(--col-width) * var(--col-span) - calc(var(--spacing) * 1));
80+
}
81+
}
82+
83+
.publish-field {
4684
.form-group {
4785
> label,
4886
.field-inner > label {
@@ -90,8 +128,11 @@ code.parent-url {
90128
@apply flex items-center;
91129
}
92130

93-
.publish-sections-section {
131+
.publish-sections .publish-sections-section {
94132
@apply mb-8;
133+
[data-cards-wrap="fields"] & {
134+
@apply mb-12;
135+
}
95136
}
96137

97138
.publish-section-header {

resources/css/elements/forms.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ input[type='file'] {
305305
/* Form groups
306306
========================================================================== */
307307
.form-group {
308-
@apply m-0 pb-3 last:pb-0;
308+
/* @apply m-0 pb-3 last:pb-0; */
309309

310310
label {
311311
font-weight: 500;

resources/js/bootstrap/globals.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ export function tailwind_width_class(width) {
6767
return `${widths[width] || 'w-full'}`;
6868
}
6969

70+
export function field_width_class(width) {
71+
const widths = {
72+
25: 'field-w-25',
73+
33: 'field-w-33',
74+
50: 'field-w-50',
75+
66: 'field-w-66',
76+
75: 'field-w-75',
77+
100: 'field-w-100',
78+
};
79+
80+
return `${widths[width] || 'field-w-100'}`;
81+
}
82+
7083
export function markdown(value) {
7184
return marked(value);
7285
}

resources/js/components/collections/EditForm.vue

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<publish-container
3-
v-if="blueprint"
43
class="max-w-5xl mx-auto"
4+
v-if="blueprint"
55
ref="container"
66
name="collection"
77
reference="collection"
@@ -12,12 +12,17 @@
1212
:site="site"
1313
@updated="values = $event"
1414
v-slot="{ setFieldValue, setFieldMeta }"
15+
data-cards-wrap="fields"
1516
>
1617
<div>
1718
<Header :title="__(editTitle ?? 'Configure Collection')" icon="cog">
1819
<Button variant="primary" @click="submit">{{ __('Save') }}</Button>
1920
</Header>
20-
<configure-tabs @updated="setFieldValue" @meta-updated="setFieldMeta" :enable-sidebar="false" />
21+
<configure-tabs
22+
@updated="setFieldValue"
23+
@meta-updated="setFieldMeta"
24+
:enable-sidebar="false"
25+
/>
2126
</div>
2227
</publish-container>
2328
</template>
@@ -47,6 +52,12 @@ export default {
4752
};
4853
},
4954
55+
provide() {
56+
return {
57+
wrapFieldsInCards: true,
58+
};
59+
},
60+
5061
methods: {
5162
clearErrors() {
5263
this.error = null;

resources/js/components/configure/Tabs.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
export default {
2525
emits: ['updated', 'meta-updated', 'synced', 'desynced', 'focus', 'blur'],
2626
27-
inject: ['store', 'storeName'],
27+
inject: ['store', 'storeName', 'wrapFieldsInCards'],
2828
2929
props: {
3030
readOnly: Boolean,

resources/js/components/fields/Settings.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export default {
190190
191191
methods: {
192192
configFieldClasses(field) {
193-
return [`form-group p-4 m-0 ${field.type}-fieldtype`, tailwind_width_class(field.width)];
193+
return [`form-group p-4 m-0 ${field.type}-fieldtype`, field_width_class(field.width)];
194194
},
195195
196196
getFieldValue(handle) {

resources/js/components/publish/Field.vue

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
@loaded="metaLoaded"
77
v-slot="{ meta, value, loading: loadingMeta }"
88
>
9-
<div :class="classes">
9+
<component :is="wrapFieldsInCards ? 'Card' : 'div'" :class="classes">
1010
<div class="field-inner">
1111
<label
1212
v-if="showLabel"
@@ -16,12 +16,12 @@
1616
>
1717
<span
1818
v-if="showLabelText"
19-
class="ltr:mr-1 rtl:ml-1"
19+
class="me-1"
2020
:class="{ 'text-gray-600': syncable && isSynced }"
2121
v-text="__(labelText)"
2222
v-tooltip="{ content: config.handle, delay: 500, autoHide: false }"
2323
/>
24-
<i class="required ltr:mr-1 rtl:ml-1" v-if="showLabelText && config.required">*</i>
24+
<i class="required me-1" v-if="showLabelText && config.required">*</i>
2525
<avatar
2626
v-if="isLocked"
2727
:user="lockingUser"
@@ -30,13 +30,13 @@
3030
/>
3131
<span
3232
v-if="isReadOnly && !isTab && !isSection"
33-
class="mt-0.5 text-2xs font-normal text-gray-500 dark:text-dark-200 ltr:mr-1 rtl:ml-1"
33+
class="mt-0.5 text-2xs font-normal text-gray-500 dark:text-dark-200 me-1"
3434
>
3535
{{ isLocked ? __('Locked') : __('Read Only') }}
3636
</span>
3737
<svg-icon
3838
name="translate"
39-
class="h-4 w-4 text-gray-600 ltr:mr-1 rtl:ml-1"
39+
class="size-4 text-gray-600 me-1"
4040
v-if="isLocalizable && !isTab"
4141
v-tooltip.top="__('Localizable field')"
4242
/>
@@ -50,7 +50,7 @@
5050
>
5151
<svg-icon
5252
name="light/hyperlink"
53-
class="mb-1 h-4 w-4 text-gray-600 ltr:mr-1.5 rtl:ml-1.5"
53+
class="mb-1 size-4 text-gray-600 me-1.5"
5454
v-tooltip.top="__('messages.field_synced_with_origin')"
5555
/>
5656
</button>
@@ -64,15 +64,14 @@
6464
>
6565
<svg-icon
6666
name="light/hyperlink-broken"
67-
class="mb-1 h-4 w-4 text-gray-600 ltr:mr-1.5 rtl:ml-1.5"
67+
class="mb-1 size-4 text-gray-600 me-1.5"
6868
v-tooltip.top="__('messages.field_desynced_from_origin')"
6969
/>
7070
</button>
7171
</label>
7272

73-
<div
74-
class="help-block"
75-
:class="{ '-mt-2': showLabel }"
73+
<ui-description
74+
:class="{ '-mt-1 mb-2': showLabel }"
7675
v-if="instructions && config.instructions_position !== 'below'"
7776
v-html="instructions"
7877
/>
@@ -105,25 +104,30 @@
105104
<!-- TODO: name prop should include prefixing when used recursively like inside a grid. -->
106105
</slot>
107106

108-
<div
109-
class="help-block mt-2"
107+
<ui-description
108+
class="mt-2"
110109
v-if="instructions && config.instructions_position === 'below'"
111110
v-html="instructions"
112111
/>
113112

114113
<div v-if="hasError">
115114
<small class="help-block mb-0 mt-2 text-red-500" v-for="(error, i) in errors" :key="i" v-text="error" />
116115
</div>
117-
</div>
116+
</component>
118117
</publish-field-meta>
119118
</template>
120119

121120
<script>
121+
import { Card } from '@statamic/ui';
122122
import { marked } from 'marked';
123123
import titleize from '../../util/titleize';
124124
import deslugify from '../../util/deslugify';
125125
126126
export default {
127+
components: {
128+
Card,
129+
},
130+
127131
props: {
128132
config: {
129133
type: Object,
@@ -145,6 +149,7 @@ export default {
145149
inject: {
146150
store: {},
147151
isInsideConfigFields: { default: false },
152+
wrapFieldsInCards: { default: false },
148153
},
149154
150155
data() {
@@ -195,7 +200,7 @@ export default {
195200
`${this.config.component || this.config.type}-fieldtype`,
196201
,
197202
this.isReadOnly ? 'read-only-field' : '',
198-
this.isInsideConfigFields ? 'config-field' : `${tailwind_width_class(this.config.width)}`,
203+
this.isInsideConfigFields ? 'config-field' : `${field_width_class(this.config.width)}`,
199204
this.showLabel ? 'has-field-label' : '',
200205
this.shouldShowFieldActions ? 'has-field-dropdown' : '',
201206
this.config.classes || '',

resources/js/components/publish/Fields.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="publish-fields @container gap-3 lg:gap-6">
2+
<div class="publish-fields @container">
33
<publish-field
44
v-for="field in fields"
55
v-show="showField(field)"

resources/js/components/publish/Sections.vue

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<template>
22
<div class="publish-sections">
33
<div class="publish-sections-section" v-for="(section, i) in visibleSections" :key="i">
4-
<CardPanel :heading="__(section.display) || heading" :description="__(section.instructions) || description">
4+
<Panel :heading="__(section.display) || heading" :description="__(section.instructions) || description">
5+
<component :is="wrapperComponent">
56
<publish-fields
67
:fields="section.fields"
78
:read-only="readOnly"
@@ -13,23 +14,25 @@
1314
@desynced="$emit('desynced', $event)"
1415
@focus="$emit('focus', $event)"
1516
@blur="$emit('blur', $event)"
16-
/>
17-
</CardPanel>
17+
/>
18+
</component>
19+
</Panel>
1820
</div>
1921
</div>
2022
</template>
2123

2224
<script>
2325
import { ValidatesFieldConditions } from '../field-conditions/FieldConditions.js';
24-
import { CardPanel } from '@statamic/ui';
26+
import { Panel, Card } from '@statamic/ui';
2527
2628
export default {
2729
emits: ['updated', 'meta-updated', 'synced', 'desynced', 'focus', 'blur'],
2830
2931
mixins: [ValidatesFieldConditions],
3032
3133
components: {
32-
CardPanel,
34+
Panel,
35+
Card,
3336
},
3437
3538
props: {
@@ -42,7 +45,10 @@ export default {
4245
namePrefix: { type: String, default: '' },
4346
},
4447
45-
inject: ['publishContainer'],
48+
inject: {
49+
publishContainer: {},
50+
wrapFieldsInCards: { default: false }
51+
},
4652
4753
computed: {
4854
values() {
@@ -56,6 +62,10 @@ export default {
5662
visibleSections() {
5763
return this.sections.filter((section) => this.sectionHasVisibleFields(section));
5864
},
65+
66+
wrapperComponent() {
67+
return this.wrapFieldsInCards ? 'div' : 'Card';
68+
},
5969
},
6070
6171
methods: {

resources/js/components/ui/Panel/Panel.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
<script setup>
2+
import { PanelHeader, Heading, Subheading } from '@statamic/ui';
3+
4+
const props = defineProps({
5+
heading: { type: String, default: null },
6+
subheading: { type: String, default: null },
7+
});
8+
</script>
9+
110
<template>
211
<div
312
:class="[
@@ -6,6 +15,10 @@
615
]"
716
data-ui-panel
817
>
18+
<PanelHeader v-if="heading">
19+
<Heading v-html="heading" />
20+
<Subheading v-if="subheading" v-html="subheading" />
21+
</PanelHeader>
922
<slot />
1023
</div>
1124
</template>

0 commit comments

Comments
 (0)