Browse Source

Feature: Mobile - redesign Form fields

Vladimir Sheremet 2 years ago
parent
commit
77215e39b4

+ 17 - 0
app/frontend/apps/mobile/components/Form/FormGroup.vue

@@ -0,0 +1,17 @@
+<!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->
+
+<template>
+  <div class="mobile-group mb-4 rounded-xl bg-gray-500">
+    <slot />
+  </div>
+</template>
+
+<style lang="scss">
+.mobile-group {
+  .formkit-outer:not(:last-child) {
+    .formkit-inner {
+      @apply border-b border-white/10;
+    }
+  }
+}
+</style>

+ 15 - 0
app/frontend/apps/mobile/form/composable.ts

@@ -0,0 +1,15 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { FormSchemaNode } from '@shared/components/Form'
+
+export function defineFormSchema(schema: FormSchemaNode[]): FormSchemaNode[] {
+  const needGroup = schema.every((node) => !('isLayout' in node))
+  if (!needGroup) return schema
+  return [
+    {
+      isLayout: true,
+      component: 'FormGroup',
+      children: schema,
+    },
+  ]
+}

+ 4 - 0
app/frontend/apps/mobile/form/index.ts

@@ -9,6 +9,7 @@ import type {
   InitializeAppForm,
 } from '@shared/types/form'
 import type { ImportGlobEagerOutput } from '@shared/types/utils'
+import FormGroup from '@mobile/components/Form/FormGroup.vue'
 import getCoreClasses from './theme/global/getCoreClasses'
 
 const pluginModules: ImportGlobEagerOutput<FormKitPlugin> =
@@ -25,6 +26,9 @@ const initializeForm: InitializeAppForm = (app: App) => {
     extensions: themeExtensionModules,
   }
 
+  // TODO figure out a way to put in inside "additionalComponentLibrary" in Form.vue
+  app.component('FormGroup', FormGroup)
+
   mainInitializeForm(app, undefined, fieldModules, plugins, theme)
 }
 

+ 26 - 4
app/frontend/apps/mobile/form/theme/global/getCoreClasses.ts

@@ -9,10 +9,32 @@ export const addFloatingLabel = (classes: Classes = {}): Classes => {
   const labelClass = classes.label || ''
   return {
     outer: `${classes.outer || ''} floating-input`,
-    wrapper: `${classes.wrapper || ''} relative`,
+    wrapper: `${classes.wrapper || ''} relative px-3`,
     inner: 'flex',
-    input: `${inputClass} w-full h-14 text-sm bg-gray-500 rounded-xl border-none focus:outline-none placeholder:text-transparent focus-within:pt-8 formkit-populated:pt-8`,
-    label: `${labelClass} absolute top-0 ltr:left-0 rtl:right-0 py-5 px-3 h-14 text-base transition-all duration-100 ease-in-out origin-left pointer-events-none formkit-populated:-translate-y-3 rtl:formkit-populated:translate-x-1 rtl:formkit-populated:translate-x-6 formkit-populated:scale-75 formkit-populated:opacity-75 formkit-required:required`,
+    input: `
+      ${inputClass}
+      w-full
+      px-0
+      h-14
+      text-sm
+      bg-transparent
+      border-none
+      focus:outline-none
+      placeholder:text-transparent
+      focus-within:pt-8
+      formkit-populated:pt-8
+    `,
+    label: `
+      ${labelClass}
+      absolute top-0 ltr:left-0 rtl:right-0
+      py-5 px-3 h-14
+      text-base
+      transition-all duration-100 ease-in-out origin-left
+      pointer-events-none
+      formkit-populated:-translate-y-3 formkit-populated:translate-x-6
+      formkit-populated:scale-75 formkit-populated:opacity-75
+      formkit-required:required
+    `,
   }
 }
 
@@ -20,7 +42,7 @@ export const addDateLabel = (classes: Classes = {}): Classes => {
   const newClasses = addFloatingLabel(classes)
   return {
     ...newClasses,
-    inner: 'flex flex-col items-center bg-gray-500 rounded-xl',
+    inner: 'flex flex-col items-center',
   }
 }
 

+ 9 - 50
app/frontend/apps/mobile/modules/home/views/Home.vue

@@ -6,53 +6,6 @@ import useAuthenticationStore from '@shared/stores/authentication'
 import { useNotifications } from '@shared/components/CommonNotifications'
 import { MenuItem } from '@mobile/components/CommonSectionMenu'
 import CommonSectionMenu from '@mobile/components/CommonSectionMenu/CommonSectionMenu.vue'
-import Form from '@shared/components/Form/Form.vue'
-import { FormSchemaNode } from '@shared/components/Form'
-
-const schema: FormSchemaNode[] = [
-  {
-    type: 'text',
-    name: 'input',
-    label: 'Some_Input',
-    props: {
-      link: '/tickets',
-    },
-  },
-  {
-    type: 'tel',
-    name: 'input2',
-    props: {
-      link: '/tickets',
-    },
-    label: 'Another_Input',
-  },
-  {
-    type: 'date',
-    name: 'date',
-    label: 'Date_Input',
-    props: {
-      link: '/tickets',
-    },
-  },
-  {
-    type: 'select',
-    name: 'select',
-    label: 'Select',
-    props: {
-      link: '/tickets',
-      options: [{ label: 'Label', value: 1 }],
-    },
-  },
-  {
-    type: 'treeselect',
-    name: 'treeselect',
-    label: 'Treeselect',
-    props: {
-      options: [{ label: 'Label', value: 1 }],
-      link: '/tickets',
-    },
-  },
-]
 
 const menu: MenuItem[] = [
   { type: 'link', link: '/tickets', title: __('All Tickets') },
@@ -103,16 +56,22 @@ const logoutMenu: MenuItem[] = [
     title: __('Logout'),
   },
 ]
+
+const testingMenu: MenuItem[] = [
+  {
+    type: 'link',
+    link: '/test',
+    title: 'Testing',
+  },
+]
 </script>
 
 <template>
   <div class="pt-10">
-    <div class="p-4">
-      <Form :schema="schema" />
-    </div>
     <div class="flex w-full items-center justify-center text-3xl font-bold">
       {{ i18n.t('Home') }}
     </div>
+    <CommonSectionMenu :items="testingMenu" />
     <CommonSectionMenu :items="menu" action-title="Edit" />
     <CommonSectionMenu
       :items="ticketOverview"

+ 22 - 0
app/frontend/apps/mobile/modules/test/routes.ts

@@ -0,0 +1,22 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+/* eslint-disable zammad/zammad-detect-translatable-string */
+
+import type { RouteRecordRaw } from 'vue-router'
+
+const routes: RouteRecordRaw[] = [
+  {
+    path: '/test',
+    name: 'TestOverview',
+    props: true,
+    component: () => import('./views/TestOverview.vue'),
+    meta: {
+      title: 'Home',
+      requiresAuth: true,
+      requiredPermission: ['*'],
+      hasBottomNavigation: true,
+      level: 2,
+    },
+  },
+]
+
+export default routes

+ 72 - 0
app/frontend/apps/mobile/modules/test/views/TestOverview.vue

@@ -0,0 +1,72 @@
+<!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->
+
+<script setup lang="ts">
+/* eslint-disable zammad/zammad-detect-translatable-string */
+
+import Form from '@shared/components/Form/Form.vue'
+import { defineFormSchema } from '@mobile/form/composable'
+
+const linkSchemaRaw = [
+  {
+    type: 'date',
+    name: 'date',
+    label: 'Date_Input',
+    props: {
+      link: '/tickets',
+    },
+  },
+  {
+    type: 'select',
+    name: 'select',
+    label: 'Select',
+    props: {
+      link: '/tickets',
+      options: [{ label: 'Label', value: 1 }],
+    },
+  },
+  {
+    type: 'treeselect',
+    name: 'treeselect',
+    label: 'Treeselect',
+    props: {
+      options: [{ label: 'Label', value: 1 }],
+      link: '/tickets',
+    },
+  },
+]
+const linkSchemas = defineFormSchema(linkSchemaRaw)
+
+const schema = defineFormSchema([
+  {
+    isLayout: true,
+    component: 'FormGroup',
+    props: {
+      columns: 2,
+    },
+    children: [
+      {
+        type: 'text',
+        name: 'text22',
+        label: 'Some_Label',
+      },
+      {
+        type: 'text',
+        name: 'text23',
+        label: 'Some Label3',
+      },
+    ],
+  },
+  {
+    isLayout: true,
+    component: 'FormGroup',
+    children: linkSchemaRaw,
+  },
+])
+</script>
+
+<template>
+  <div class="p-4">
+    <Form :schema="linkSchemas" />
+    <Form :schema="schema" />
+  </div>
+</template>

+ 17 - 0
app/frontend/apps/mobile/router/index.ts

@@ -14,12 +14,29 @@ const routeModules: Record<string, RoutesModule> = import.meta.globEager(
 const mainRoutes: Array<RouteRecordRaw> = []
 const childRoutes: Array<RouteRecordRaw> = []
 
+const names = new Set<string | symbol>()
+
 const handleRoutes = (routes: Array<RouteRecordRaw>, isMainRoute = false) => {
   if (isMainRoute) {
     mainRoutes.push(...routes)
   } else {
     childRoutes.push(...routes)
   }
+
+  if (import.meta.env.PROD) return
+
+  // for debugging routes, vue-router doesn't do this automatically
+  routes.forEach((route) => {
+    if (!route.name) return
+
+    if (names.has(route.name)) {
+      console.error(
+        `Duplicate route name: ${String(route.name)} for ${route.path}`,
+      )
+    } else {
+      names.add(route.name)
+    }
+  })
 }
 
 Object.values(routeModules).forEach((module: RoutesModule) => {

+ 1 - 0
app/frontend/shared/components/Form/Form.vue

@@ -172,6 +172,7 @@ const formConfig = computed(() => {
 // Define the additional component library for the used components which are not form fields.
 const additionalComponentLibrary = {
   FormLayout: markRaw(FormLayout) as ConcreteComponent,
+  // somehow add mobile libraries, like FormGroup instead of app.component
 }
 
 // Define the static schema, which will be filled with the real fields from the `schemaData`.

+ 22 - 0
app/frontend/shared/components/Form/FormFieldLink.vue

@@ -0,0 +1,22 @@
+<!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->
+
+<script setup lang="ts">
+import { RouteLocationRaw } from 'vue-router'
+
+defineProps<{
+  link: RouteLocationRaw
+}>()
+</script>
+
+<template>
+  <div class="h-14 w-14 py-2 focus:outline-none">
+    <CommonLink
+      v-if="link"
+      :link="link"
+      class="flex h-full w-full items-center justify-center border-white/10 ltr:border-l ltr:pl-1 rtl:border-r rtl:pr-1"
+      open-in-new-tab
+    >
+      <CommonIcon name="external" size="small" />
+    </CommonLink>
+  </div>
+</template>

Some files were not shown because too many files changed in this diff