diff --git a/src/components/v-form/form-field-interface.vue b/src/components/v-form/form-field-interface.vue new file mode 100644 index 0000000000..0f01f0727b --- /dev/null +++ b/src/components/v-form/form-field-interface.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/components/v-form/form-field-label.vue b/src/components/v-form/form-field-label.vue new file mode 100644 index 0000000000..c5f8e5c721 --- /dev/null +++ b/src/components/v-form/form-field-label.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/v-form/form-field-menu.vue b/src/components/v-form/form-field-menu.vue new file mode 100644 index 0000000000..fb806a57da --- /dev/null +++ b/src/components/v-form/form-field-menu.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/components/v-form/form-field.vue b/src/components/v-form/form-field.vue new file mode 100644 index 0000000000..05281f04b7 --- /dev/null +++ b/src/components/v-form/form-field.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/components/v-form/v-form.test.ts b/src/components/v-form/v-form.test.ts deleted file mode 100644 index a80aeaf647..0000000000 --- a/src/components/v-form/v-form.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import VueCompositionAPI from '@vue/composition-api'; - -import VForm from './v-form.vue'; - -const localVue = createLocalVue(); -localVue.use(VueCompositionAPI); - -describe('Components / Form', () => { - it('Renders', () => { - const component = shallowMount(VForm, { localVue, propsData: { collection: 'test' } }); - expect(component.isVueInstance()).toBe(true); - }); -}); diff --git a/src/components/v-form/v-form.vue b/src/components/v-form/v-form.vue index 523e0af33d..a1da796e6a 100644 --- a/src/components/v-form/v-form.vue +++ b/src/components/v-form/v-form.vue @@ -1,104 +1,20 @@ @@ -109,16 +25,18 @@ import { Field } from '@/stores/fields/types'; import { useElementSize } from '@/composables/use-element-size'; import { isEmpty } from '@/utils/is-empty'; import { clone } from 'lodash'; -import { FormField } from './types'; +import { FormField as TFormField } from './types'; import interfaces from '@/interfaces'; import marked from 'marked'; import getDefaultInterfaceForType from '@/utils/get-default-interface-for-type'; +import FormField from './form-field.vue'; type FieldValues = { [field: string]: any; }; export default defineComponent({ + components: { FormField }, model: { prop: 'edits', }, @@ -128,7 +46,7 @@ export default defineComponent({ default: undefined, }, fields: { - type: Array as PropType, + type: Array as PropType, default: undefined, }, initialValues: { @@ -151,6 +69,10 @@ export default defineComponent({ type: [String, Number], required: true, }, + disabled: { + type: Boolean, + default: false, + }, }, setup(props, { emit }) { const el = ref(null); @@ -233,11 +155,11 @@ export default defineComponent({ } if (interfaceUsed?.hideLabel === true) { - (field as FormField).hideLabel = true; + (field as TFormField).hideLabel = true; } if (interfaceUsed?.hideLoader === true) { - (field as FormField).hideLoader = true; + (field as TFormField).hideLoader = true; } return field; @@ -276,7 +198,16 @@ export default defineComponent({ return null; }); - return { formFields, gridClass }; + return { formFields, gridClass, isDisabled }; + + function isDisabled(field: Field) { + return ( + props.loading || + props.disabled === true || + field.readonly === true || + (props.batchMode && batchActiveFields.value.includes(field.field) === false) + ); + } } function setValue(field: Field, value: any) { @@ -359,69 +290,4 @@ body { grid-column: start / fill; } } - -.interface { - position: relative; - - .v-skeleton-loader { - position: absolute; - top: 0; - left: 0; - z-index: 2; - width: 100%; - height: 100%; - } - - &.subdued { - opacity: 0.5; - } -} - -.label { - position: relative; - display: flex; - width: max-content; - margin-bottom: 8px; - cursor: pointer; - - &.readonly { - cursor: not-allowed; - } - - .v-checkbox { - margin-right: 4px; - } - - .required { - --v-icon-color: var(--primary); - - margin-left: -3px; - } - - .ctx-arrow { - position: absolute; - top: -3px; - right: -20px; - color: var(--foreground-subdued); - opacity: 0; - transition: opacity var(--fast) var(--transition); - - &.active { - opacity: 1; - } - } - - &:hover { - .ctx-arrow { - opacity: 1; - } - } -} - -.note { - display: block; - margin-top: 4px; - color: var(--foreground-subdued); - font-style: italic; -} diff --git a/src/components/v-input/v-input.vue b/src/components/v-input/v-input.vue index 44a88a6a34..f1d7f9a9d3 100644 --- a/src/components/v-input/v-input.vue +++ b/src/components/v-input/v-input.vue @@ -33,12 +33,14 @@ name="keyboard_arrow_up" class="step-up" @click="stepUp" + :disabled="disabled" />
@@ -187,6 +189,7 @@ export default defineComponent({ function stepUp() { if (!input.value) return; + if (props.disabled === true) return; if (props.value < props.max) { input.value.stepUp(); @@ -196,6 +199,7 @@ export default defineComponent({ function stepDown() { if (!input.value) return; + if (props.disabled === true) return; if (props.value > props.min) { input.value.stepDown(); diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 5c915ae593..6d2fc8caf5 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -18,6 +18,7 @@ import InterfaceOneToMany from './one-to-many'; import InterfaceHash from './hash'; import InterfaceSlug from './slug'; import InterfaceUser from './user'; +import InterfaceRepeater from './repeater'; export const interfaces = [ InterfaceTextInput, @@ -40,6 +41,7 @@ export const interfaces = [ InterfaceHash, InterfaceSlug, InterfaceUser, + InterfaceRepeater, ]; export default interfaces; diff --git a/src/interfaces/repeater/index.ts b/src/interfaces/repeater/index.ts new file mode 100644 index 0000000000..d0ac5c5ed5 --- /dev/null +++ b/src/interfaces/repeater/index.ts @@ -0,0 +1,11 @@ +import { defineInterface } from '../define'; +import InterfaceRepeater from './repeater.vue'; + +export default defineInterface(({ i18n }) => ({ + id: 'repeater', + name: i18n.t('repeater'), + icon: 'replay', + types: ['json'], + component: InterfaceRepeater, + options: [], +})); diff --git a/src/interfaces/repeater/repeater-row-form.vue b/src/interfaces/repeater/repeater-row-form.vue new file mode 100644 index 0000000000..1d92350233 --- /dev/null +++ b/src/interfaces/repeater/repeater-row-form.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/interfaces/repeater/repeater-row-header.vue b/src/interfaces/repeater/repeater-row-header.vue new file mode 100644 index 0000000000..25c8852cdf --- /dev/null +++ b/src/interfaces/repeater/repeater-row-header.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/interfaces/repeater/repeater-row.vue b/src/interfaces/repeater/repeater-row.vue new file mode 100644 index 0000000000..a674734d30 --- /dev/null +++ b/src/interfaces/repeater/repeater-row.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/interfaces/repeater/repeater.vue b/src/interfaces/repeater/repeater.vue new file mode 100644 index 0000000000..827ceedcfa --- /dev/null +++ b/src/interfaces/repeater/repeater.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/src/lang/en-US/index.json b/src/lang/en-US/index.json index ea244ae73c..252adcfb9f 100644 --- a/src/lang/en-US/index.json +++ b/src/lang/en-US/index.json @@ -177,6 +177,15 @@ "webhooks": "Webhooks", "roles": "User Roles", + "field_width": "Field Width", + "field_width_half": "Half Width (Wraps)", + "field_width_left": "Half Width (Left Only)", + "field_width_right": "Half Width (Right Only)", + "field_width_full": "Full Width", + "field_width_fill": "Fill the Page", + + "add_a_new_item": "Add a new item...", + "add_filter": "Add Filter", "user_directory": "User Directory", @@ -306,6 +315,8 @@ "color": "Color", "circle": "Circle", + "empty_item": "Empty Item", + "filter": "Filter", "advanced_filter": "Advanced Filter", "operators": { @@ -684,12 +695,6 @@ "field_setup_options": "All set! Just review these interface options...", "field_type": "Field Type", "field_updated": "Field Updated", - "field_width": "Field Width", - "field_width_half": "Half Width (Wraps)", - "field_width_left": "Half Width (Left Only)", - "field_width_right": "Half Width (Right Only)", - "field_width_full": "Full Width", - "field_width_fill": "Fill the Page", "field_width_note": "The width of this field within the form layout. Half-widths wrap based on other fields and their sort order.", "fields_are_saved_instantly": "Saves Automatically", "fieldtypes": { diff --git a/src/styles/_type-styles.scss b/src/styles/_type-styles.scss index deaa7d9962..da0fc902c5 100644 --- a/src/styles/_type-styles.scss +++ b/src/styles/_type-styles.scss @@ -1,3 +1,5 @@ +@import './mixins/type-styles.scss'; + :root { --family-monospace: 'Fira Code', monospace; --family-serif: 'Merriweather', serif; @@ -5,31 +7,13 @@ } .type-title { - color: var(--foreground-normal); - font-weight: normal; - font-size: 24px; - font-family: var(--family-sans-serif); - font-style: normal; - line-height: 29px; - letter-spacing: -0.8px; + @include type-title; } .type-label { - color: var(--foreground-normal); - font-weight: 600; - font-size: 16px; - font-family: var(--family-sans-serif); - font-style: normal; - line-height: 19px; - letter-spacing: -0.32px; + @include type-label; } .type-text { - color: var(--foreground-normal); - font-weight: 500; - font-size: 14px; - font-family: var(--family-sans-serif); - font-style: normal; - line-height: 22px; - letter-spacing: -0.15px; + @include type-text; } diff --git a/src/styles/mixins/type-styles.scss b/src/styles/mixins/type-styles.scss new file mode 100644 index 0000000000..c103d4ee76 --- /dev/null +++ b/src/styles/mixins/type-styles.scss @@ -0,0 +1,29 @@ +@mixin type-title { + color: var(--foreground-normal); + font-weight: normal; + font-size: 24px; + font-family: var(--family-sans-serif); + font-style: normal; + line-height: 29px; + letter-spacing: -0.8px; +} + +@mixin type-label { + color: var(--foreground-normal); + font-weight: 600; + font-size: 16px; + font-family: var(--family-sans-serif); + font-style: normal; + line-height: 19px; + letter-spacing: -0.32px; +} + +@mixin type-text { + color: var(--foreground-normal); + font-weight: 500; + font-size: 14px; + font-family: var(--family-sans-serif); + font-style: normal; + line-height: 22px; + letter-spacing: -0.15px; +}