Better form themes (#465)
* Working on custom radius + input size * Fix date input clear vertical align * Moslty finished implementing small size * Polishing larger theme * Finish large theme * Added size/radius options in form editor * Darken help text, improve switch input help location * Slight form editor improvement * Fix styling * Polish of the form editor
This commit is contained in:
parent
a84abf9f72
commit
2ca2d97e8e
|
|
@ -31,6 +31,8 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest
|
|||
// Customization
|
||||
'theme' => ['required', Rule::in(Form::THEMES)],
|
||||
'width' => ['required', Rule::in(Form::WIDTHS)],
|
||||
'size' => ['required', Rule::in(Form::SIZES)],
|
||||
'border_radius' => ['required', Rule::in(Form::BORDER_RADIUS)],
|
||||
'cover_picture' => 'url|nullable',
|
||||
'logo_picture' => 'url|nullable',
|
||||
'dark_mode' => ['required', Rule::in(Form::DARK_MODE_VALUES)],
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ class Form extends Model implements CachableAttributes
|
|||
|
||||
public const DARK_MODE_VALUES = ['auto', 'light', 'dark'];
|
||||
|
||||
public const SIZES = ['sm','md','lg'];
|
||||
|
||||
public const BORDER_RADIUS = ['none','small','full'];
|
||||
|
||||
public const THEMES = ['default', 'simple', 'notion'];
|
||||
|
||||
public const WIDTHS = ['centered', 'full'];
|
||||
|
|
@ -49,6 +53,8 @@ class Form extends Model implements CachableAttributes
|
|||
|
||||
// Customization
|
||||
'custom_domain',
|
||||
'size',
|
||||
'border_radius',
|
||||
'theme',
|
||||
'width',
|
||||
'cover_picture',
|
||||
|
|
|
|||
|
|
@ -10,9 +10,16 @@
|
|||
:disabled="disabled ? true : null"
|
||||
:name="name"
|
||||
:color="color"
|
||||
:theme="theme"
|
||||
>
|
||||
<slot name="label">
|
||||
{{ label }}
|
||||
<slot
|
||||
name="label"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
theme.SelectInput.fontSize,
|
||||
]"
|
||||
>{{ label }}</span>
|
||||
<span
|
||||
v-if="required"
|
||||
class="text-red-500 required-dot"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<div
|
||||
:class="[
|
||||
theme.CodeInput.input,
|
||||
theme.CodeInput.borderRadius,
|
||||
{
|
||||
'!ring-red-500 !ring-2 !border-transparent': hasError,
|
||||
'!cursor-not-allowed !bg-gray-200': disabled,
|
||||
|
|
|
|||
|
|
@ -10,18 +10,18 @@
|
|||
:popper="{ placement: 'bottom-start' }"
|
||||
>
|
||||
<button
|
||||
ref="datepicker"
|
||||
class="cursor-pointer overflow-hidden"
|
||||
:class="inputClasses"
|
||||
:disabled="props.disabled"
|
||||
ref="datepicker"
|
||||
>
|
||||
<div class="flex items-center min-w-0">
|
||||
<div class="flex items-stretch min-w-0">
|
||||
<div
|
||||
class="flex-grow min-w-0 flex items-center gap-x-2"
|
||||
:class="[
|
||||
props.theme.default.inputSpacing.vertical,
|
||||
props.theme.default.inputSpacing.horizontal,
|
||||
{'hover:bg-gray-50 dark:hover:bg-gray-900': !props.disabled}
|
||||
props.theme.DateInput.spacing.horizontal,
|
||||
props.theme.DateInput.spacing.vertical,
|
||||
props.theme.DateInput.fontSize,
|
||||
]"
|
||||
>
|
||||
<Icon
|
||||
|
|
@ -29,16 +29,24 @@
|
|||
class="w-4 h-4 flex-shrink-0"
|
||||
dynamic
|
||||
/>
|
||||
<div class="flex-grow truncate overflow-hidden">
|
||||
<p class="flex-grow truncate h-[24px]">
|
||||
<div class="flex-grow truncate overflow-hidden flex items-center">
|
||||
<p
|
||||
v-if="formattedDatePreview"
|
||||
class="flex-grow truncate"
|
||||
>
|
||||
{{ formattedDatePreview }}
|
||||
</p>
|
||||
<p
|
||||
v-else
|
||||
class="text-transparent"
|
||||
>
|
||||
-
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-if="fromDate && !props.disabled"
|
||||
class="hover:bg-gray-50 dark:hover:bg-gray-900 border-l px-2"
|
||||
:class="[props.theme.default.inputSpacing.vertical]"
|
||||
class="hover:bg-gray-50 dark:hover:bg-gray-900 border-l px-2 flex items-center"
|
||||
@click.prevent="clear()"
|
||||
>
|
||||
<Icon
|
||||
|
|
@ -133,7 +141,7 @@ const modeledValue = computed({
|
|||
})
|
||||
|
||||
const inputClasses = computed(() => {
|
||||
const classes = [props.theme.DateInput.input, 'w-full']
|
||||
const classes = [props.theme.DateInput.input, props.theme.DateInput.borderRadius]
|
||||
if (props.disabled) {
|
||||
classes.push('!cursor-not-allowed dark:!bg-gray-600 !bg-gray-200')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@
|
|||
</template>
|
||||
<div
|
||||
v-if="cameraUpload && isInWebcam"
|
||||
class="hidden sm:block w-full min-h-40"
|
||||
class="hidden sm:block w-full"
|
||||
:class="[
|
||||
theme.fileInput.minHeight
|
||||
]"
|
||||
>
|
||||
<camera-upload
|
||||
v-if="cameraUpload"
|
||||
|
|
@ -17,9 +20,17 @@
|
|||
<div
|
||||
v-else
|
||||
class="flex flex-col w-full items-center justify-center transition-colors duration-40"
|
||||
:class="[{'!cursor-not-allowed':disabled, 'cursor-pointer':!disabled,
|
||||
[theme.fileInput.inputHover.light + ' dark:'+theme.fileInput.inputHover.dark]: uploadDragoverEvent,
|
||||
['hover:'+theme.fileInput.inputHover.light +' dark:hover:'+theme.fileInput.inputHover.dark]: !loading}, theme.fileInput.input]"
|
||||
:class="[
|
||||
{'!cursor-not-allowed':disabled, 'cursor-pointer':!disabled,
|
||||
[theme.fileInput.inputHover.light + ' dark:'+theme.fileInput.inputHover.dark]: uploadDragoverEvent,
|
||||
['hover:'+theme.fileInput.inputHover.light +' dark:hover:'+theme.fileInput.inputHover.dark]: !loading},
|
||||
theme.fileInput.input,
|
||||
theme.fileInput.borderRadius,
|
||||
theme.fileInput.spacing.horizontal,
|
||||
theme.fileInput.spacing.vertical,
|
||||
theme.fileInput.fontSize,
|
||||
theme.fileInput.minHeight
|
||||
]"
|
||||
@dragover.prevent="uploadDragoverEvent=true"
|
||||
@dragleave.prevent="uploadDragoverEvent=false"
|
||||
@drop.prevent="onUploadDropEvent"
|
||||
|
|
@ -66,7 +77,7 @@
|
|||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-6 h-6"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
|
|
@ -76,7 +87,7 @@
|
|||
</svg>
|
||||
</div>
|
||||
|
||||
<p class="mt-2 text-sm text-gray-500 font-semibold select-none">
|
||||
<p class="mt-2 text-sm text-gray-500 font-medium select-none">
|
||||
Click to choose {{ multiple ? 'file(s)' : 'a file' }} or drag here
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-400 dark:text-gray-600 select-none">
|
||||
|
|
@ -129,24 +140,24 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { inputProps, useFormInput } from './useFormInput.js'
|
||||
import {inputProps, useFormInput} from './useFormInput.js'
|
||||
import InputWrapper from './components/InputWrapper.vue'
|
||||
import UploadedFile from './components/UploadedFile.vue'
|
||||
import OpenFormButton from '../open/forms/OpenFormButton.vue'
|
||||
import CameraUpload from './components/CameraUpload.vue'
|
||||
import { storeFile } from "~/lib/file-uploads.js"
|
||||
import {storeFile} from "~/lib/file-uploads.js"
|
||||
|
||||
export default {
|
||||
name: 'FileInput',
|
||||
components: { InputWrapper, UploadedFile, OpenFormButton },
|
||||
components: {InputWrapper, UploadedFile, OpenFormButton},
|
||||
mixins: [],
|
||||
props: {
|
||||
...inputProps,
|
||||
multiple: { type: Boolean, default: true },
|
||||
cameraUpload: { type: Boolean, default: false },
|
||||
mbLimit: { type: Number, default: 5 },
|
||||
accept: { type: String, default: '' },
|
||||
moveToFormAssets: { type: Boolean, default: false }
|
||||
multiple: {type: Boolean, default: true},
|
||||
cameraUpload: {type: Boolean, default: false},
|
||||
mbLimit: {type: Number, default: 5},
|
||||
accept: {type: String, default: ''},
|
||||
moveToFormAssets: {type: Boolean, default: false}
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
|
|
@ -159,7 +170,7 @@ export default {
|
|||
files: [],
|
||||
uploadDragoverEvent: false,
|
||||
loading: false,
|
||||
isInWebcam:false
|
||||
isInWebcam: false
|
||||
}),
|
||||
|
||||
computed: {
|
||||
|
|
@ -254,13 +265,13 @@ export default {
|
|||
this.uploadFileToServer(files.item(i))
|
||||
}
|
||||
},
|
||||
openWebcam(){
|
||||
if(!this.cameraUpload){
|
||||
openWebcam() {
|
||||
if (!this.cameraUpload) {
|
||||
return
|
||||
}
|
||||
this.isInWebcam = true
|
||||
},
|
||||
cameraFileUpload(file){
|
||||
cameraFileUpload(file) {
|
||||
this.isInWebcam = false
|
||||
this.isUploading = false
|
||||
this.uploadFileToServer(file)
|
||||
|
|
|
|||
|
|
@ -10,41 +10,74 @@
|
|||
class="h-6 w-6 text-nt-blue mx-auto"
|
||||
/>
|
||||
<div
|
||||
v-for="(option, index) in options"
|
||||
v-else
|
||||
:key="option[optionKey]"
|
||||
role="button"
|
||||
class="relative overflow-hidden"
|
||||
:class="[
|
||||
theme.default.input,
|
||||
'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900 flex',
|
||||
theme.default.borderRadius,
|
||||
{
|
||||
'mb-2': index !== options.length,
|
||||
'!ring-red-500 !ring-2 !border-transparent': hasError,
|
||||
'!cursor-not-allowed !bg-gray-200': disabled,
|
||||
},
|
||||
]"
|
||||
@click="onSelect(option[optionKey])"
|
||||
>
|
||||
<p class="flex-grow">
|
||||
{{ option[displayKey] }}
|
||||
</p>
|
||||
<div
|
||||
v-if="isSelected(option[optionKey])"
|
||||
class="flex items-center"
|
||||
v-for="(option, index) in options"
|
||||
v-if="options && options.length"
|
||||
:key="option[optionKey]"
|
||||
:role="multiple?'checkbox':'radio'"
|
||||
:aria-checked="isSelected(option[optionKey])"
|
||||
:class="[
|
||||
theme.FlatSelectInput.spacing.vertical,
|
||||
theme.FlatSelectInput.fontSize,
|
||||
theme.FlatSelectInput.option,
|
||||
]"
|
||||
@click="onSelect(option[optionKey])"
|
||||
>
|
||||
<svg
|
||||
:color="color"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd"
|
||||
<template v-if="multiple">
|
||||
<Icon
|
||||
v-if="isSelected(option[optionKey])"
|
||||
name="material-symbols:check-box"
|
||||
class="text-inherit"
|
||||
:color="color"
|
||||
:class="[theme.FlatSelectInput.icon]"
|
||||
/>
|
||||
</svg>
|
||||
<Icon
|
||||
v-else
|
||||
name="material-symbols:check-box-outline-blank"
|
||||
:class="[theme.FlatSelectInput.icon,theme.FlatSelectInput.unselectedIcon]"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Icon
|
||||
v-if="isSelected(option[optionKey])"
|
||||
name="material-symbols:radio-button-checked-outline"
|
||||
class="text-inherit"
|
||||
:color="color"
|
||||
:class="[theme.FlatSelectInput.icon]"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
name="material-symbols:radio-button-unchecked"
|
||||
:class="[theme.FlatSelectInput.icon,theme.FlatSelectInput.unselectedIcon]"
|
||||
/>
|
||||
</template>
|
||||
<p class="flex-grow">
|
||||
{{ option[displayKey] }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:class="[
|
||||
theme.FlatSelectInput.spacing.horizontal,
|
||||
theme.FlatSelectInput.spacing.vertical,
|
||||
theme.FlatSelectInput.fontSize,
|
||||
theme.FlatSelectInput.option,
|
||||
'!text-gray-500 !cursor-not-allowed'
|
||||
]"
|
||||
>
|
||||
No options available.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -58,7 +91,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { inputProps, useFormInput } from "./useFormInput.js"
|
||||
import {inputProps, useFormInput} from "./useFormInput.js"
|
||||
import InputWrapper from "./components/InputWrapper.vue"
|
||||
|
||||
/**
|
||||
|
|
@ -66,16 +99,16 @@ import InputWrapper from "./components/InputWrapper.vue"
|
|||
*/
|
||||
export default {
|
||||
name: "FlatSelectInput",
|
||||
components: { InputWrapper },
|
||||
components: {InputWrapper},
|
||||
|
||||
props: {
|
||||
...inputProps,
|
||||
options: { type: Array, required: true },
|
||||
optionKey: { type: String, default: "value" },
|
||||
emitKey: { type: String, default: "value" },
|
||||
displayKey: { type: String, default: "name" },
|
||||
loading: { type: Boolean, default: false },
|
||||
multiple: { type: Boolean, default: false },
|
||||
options: {type: Array, required: true},
|
||||
optionKey: {type: String, default: "value"},
|
||||
emitKey: {type: String, default: "value"},
|
||||
displayKey: {type: String, default: "name"},
|
||||
loading: {type: Boolean, default: false},
|
||||
multiple: {type: Boolean, default: false},
|
||||
},
|
||||
setup(props, context) {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -11,18 +11,24 @@
|
|||
aria-expanded="true"
|
||||
aria-labelledby="listbox-label"
|
||||
class="cursor-pointer relative w-full"
|
||||
:class="[theme.default.input, { 'ring-red-500 ring-2': hasError }]"
|
||||
:class="[
|
||||
theme.default.input,
|
||||
theme.default.spacing.horizontal,
|
||||
theme.default.spacing.vertical,
|
||||
theme.default.fontSize,
|
||||
theme.default.borderRadius,
|
||||
{ 'ring-red-500 ring-2': hasError }
|
||||
]"
|
||||
:style="inputStyle"
|
||||
@click.prevent="showUploadModal = true"
|
||||
>
|
||||
<div
|
||||
v-if="currentUrl == null"
|
||||
class="h-6 text-gray-600 dark:text-gray-400"
|
||||
class="text-gray-600 dark:text-gray-400"
|
||||
>
|
||||
Upload image
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 inline"
|
||||
class="h-5 w-5 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
|
|
@ -34,6 +40,7 @@
|
|||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
Upload image
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@
|
|||
:id="id ? id : name"
|
||||
:name="name"
|
||||
:style="inputStyle"
|
||||
class="flex items-start"
|
||||
class="flex items-stretch"
|
||||
>
|
||||
<v-select
|
||||
v-model="selectedCountryCode"
|
||||
class="w-[130px]"
|
||||
dropdown-class="w-[300px]"
|
||||
dropdown-class="max-w-[300px]"
|
||||
input-class="rounded-r-none"
|
||||
:data="countries"
|
||||
:disabled="disabled || countries.length === 1 ? true : null"
|
||||
|
|
@ -28,13 +28,13 @@
|
|||
@update:model-value="onChangeCountryCode"
|
||||
>
|
||||
<template #option="props">
|
||||
<div class="flex items-center space-x-2 hover:text-white">
|
||||
<div class="flex items-center space-x-2 max-w-full">
|
||||
<country-flag
|
||||
size="normal"
|
||||
class="!-mt-[9px]"
|
||||
class="!-mt-[9px] rounded"
|
||||
:country="props.option.code"
|
||||
/>
|
||||
<span class="grow">{{ props.option.name }}</span>
|
||||
<span class="grow truncate">{{ props.option.name }}</span>
|
||||
<span>{{ props.option.dial_code }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
>
|
||||
<country-flag
|
||||
size="normal"
|
||||
class="!-mt-[9px]"
|
||||
class="!-mt-[9px] rounded"
|
||||
:country="props.option.code"
|
||||
/>
|
||||
<span>{{ props.option.dial_code }}</span>
|
||||
|
|
@ -58,6 +58,10 @@
|
|||
:disabled="disabled ? true : null"
|
||||
:class="[
|
||||
theme.default.input,
|
||||
theme.default.spacing.horizontal,
|
||||
theme.default.spacing.vertical,
|
||||
theme.default.fontSize,
|
||||
theme.default.borderRadius,
|
||||
{
|
||||
'!ring-red-500 !ring-2': hasError,
|
||||
'!cursor-not-allowed !bg-gray-200': disabled,
|
||||
|
|
|
|||
|
|
@ -20,16 +20,10 @@
|
|||
@mouseenter="onMouseHover(i)"
|
||||
@mouseleave="hoverRating = -1"
|
||||
>
|
||||
<svg
|
||||
class="w-8 h-8"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
|
||||
/>
|
||||
</svg>
|
||||
<Icon
|
||||
name="heroicons:star-20-solid"
|
||||
:class="theme.RatingInput.size"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
'!cursor-not-allowed !bg-gray-200': disabled,
|
||||
},
|
||||
theme.RichTextAreaInput.input,
|
||||
theme.RichTextAreaInput.borderRadius,
|
||||
]"
|
||||
:editor-toolbar="editorToolbar"
|
||||
class="rich-editor resize-y"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@
|
|||
:class="[
|
||||
{ 'font-semibold': compVal === i },
|
||||
theme.ScaleInput.button,
|
||||
theme.ScaleInput.borderRadius,
|
||||
theme.ScaleInput.spacing.horizontal,
|
||||
theme.ScaleInput.spacing.vertical,
|
||||
theme.ScaleInput.fontSize,
|
||||
compVal !== i ? unselectedButtonClass : '',
|
||||
]"
|
||||
:style="btnStyle(i === compVal)"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,12 @@
|
|||
<template #selected="{ option }">
|
||||
<template v-if="multiple">
|
||||
<div class="flex items-center truncate mr-6">
|
||||
<span class="truncate">
|
||||
<span
|
||||
class="truncate"
|
||||
:class="[
|
||||
theme.SelectInput.fontSize,
|
||||
]"
|
||||
>
|
||||
{{ getOptionNames(selectedValues).join(', ') }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -45,7 +50,13 @@
|
|||
:option-name="getOptionName(option)"
|
||||
>
|
||||
<div class="flex items-center truncate mr-6">
|
||||
<div>{{ getOptionName(option) }}</div>
|
||||
<div
|
||||
:class="[
|
||||
theme.SelectInput.fontSize,
|
||||
]"
|
||||
>
|
||||
{{ getOptionName(option) }}
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
|
|
@ -57,7 +68,12 @@
|
|||
:selected="selected"
|
||||
>
|
||||
<span class="flex">
|
||||
<p class="flex-grow">
|
||||
<p
|
||||
class="flex-grow"
|
||||
:class="[
|
||||
theme.SelectInput.fontSize,
|
||||
]"
|
||||
>
|
||||
{{ option.name }}
|
||||
</p>
|
||||
<span
|
||||
|
|
@ -85,7 +101,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { inputProps, useFormInput } from './useFormInput.js'
|
||||
import {inputProps, useFormInput} from './useFormInput.js'
|
||||
import InputWrapper from './components/InputWrapper.vue'
|
||||
|
||||
/**
|
||||
|
|
@ -93,41 +109,41 @@ import InputWrapper from './components/InputWrapper.vue'
|
|||
*/
|
||||
export default {
|
||||
name: 'SelectInput',
|
||||
components: { InputWrapper },
|
||||
components: {InputWrapper},
|
||||
|
||||
props: {
|
||||
...inputProps,
|
||||
options: { type: Array, required: true },
|
||||
optionKey: { type: String, default: 'value' },
|
||||
emitKey: { type: String, default: 'value' },
|
||||
displayKey: { type: String, default: 'name' },
|
||||
loading: { type: Boolean, default: false },
|
||||
multiple: { type: Boolean, default: false },
|
||||
searchable: { type: Boolean, default: false },
|
||||
clearable: { type: Boolean, default: false },
|
||||
allowCreation: { type: Boolean, default: false },
|
||||
dropdownClass: { type: String, default: 'w-full' },
|
||||
remote: { type: Function, default: null }
|
||||
options: {type: Array, required: true},
|
||||
optionKey: {type: String, default: 'value'},
|
||||
emitKey: {type: String, default: 'value'},
|
||||
displayKey: {type: String, default: 'name'},
|
||||
loading: {type: Boolean, default: false},
|
||||
multiple: {type: Boolean, default: false},
|
||||
searchable: {type: Boolean, default: false},
|
||||
clearable: {type: Boolean, default: false},
|
||||
allowCreation: {type: Boolean, default: false},
|
||||
dropdownClass: {type: String, default: 'w-full'},
|
||||
remote: {type: Function, default: null}
|
||||
},
|
||||
setup (props, context) {
|
||||
setup(props, context) {
|
||||
return {
|
||||
...useFormInput(props, context)
|
||||
}
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
additionalOptions: [],
|
||||
selectedValues: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
finalOptions () {
|
||||
finalOptions() {
|
||||
return this.options.concat(this.additionalOptions)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
compVal: {
|
||||
handler (newVal, oldVal) {
|
||||
handler(newVal, oldVal) {
|
||||
if (!oldVal) {
|
||||
this.handleCompValChanged()
|
||||
}
|
||||
|
|
@ -135,32 +151,32 @@ export default {
|
|||
immediate: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.handleCompValChanged()
|
||||
},
|
||||
methods: {
|
||||
getOptionName (val) {
|
||||
getOptionName(val) {
|
||||
const option = this.finalOptions.find((optionCandidate) => {
|
||||
return optionCandidate[this.optionKey] === val
|
||||
})
|
||||
if (option) return option[this.displayKey]
|
||||
return null
|
||||
},
|
||||
getOptionNames (values) {
|
||||
getOptionNames(values) {
|
||||
return values.map(val => {
|
||||
return this.getOptionName(val)
|
||||
})
|
||||
},
|
||||
updateModelValue (newValues) {
|
||||
updateModelValue(newValues) {
|
||||
if (newValues === null) newValues = []
|
||||
this.selectedValues = newValues
|
||||
},
|
||||
updateOptions (newItem) {
|
||||
updateOptions(newItem) {
|
||||
if (newItem) {
|
||||
this.additionalOptions.push(newItem)
|
||||
}
|
||||
},
|
||||
handleCompValChanged () {
|
||||
handleCompValChanged() {
|
||||
if (this.compVal) {
|
||||
this.selectedValues = this.compVal
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,17 @@
|
|||
<VueSignaturePad
|
||||
ref="signaturePad"
|
||||
:class="[
|
||||
theme.default.input,
|
||||
theme.SignatureInput.input,
|
||||
theme.SignatureInput.spacing.horizontal,
|
||||
theme.SignatureInput.spacing.vertical,
|
||||
theme.SignatureInput.fontSize,
|
||||
theme.SignatureInput.borderRadius,
|
||||
theme.SignatureInput.minHeight,
|
||||
{
|
||||
'!ring-red-500 !ring-2 !border-transparent': hasError,
|
||||
'!cursor-not-allowed !bg-gray-200': disabled,
|
||||
},
|
||||
]"
|
||||
height="150px"
|
||||
:name="name"
|
||||
:options="{ onEnd }"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@
|
|||
:disabled="disabled ? true : null"
|
||||
:class="[
|
||||
theme.default.input,
|
||||
theme.default.borderRadius,
|
||||
theme.default.spacing.horizontal,
|
||||
theme.default.spacing.vertical,
|
||||
theme.default.fontSize,
|
||||
{
|
||||
'!ring-red-500 !ring-2 !border-transparent': hasError,
|
||||
'!cursor-not-allowed !bg-gray-200': disabled,
|
||||
|
|
@ -48,18 +52,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { inputProps, useFormInput } from "./useFormInput.js"
|
||||
import {inputProps, useFormInput} from "./useFormInput.js"
|
||||
import InputWrapper from "./components/InputWrapper.vue"
|
||||
|
||||
export default {
|
||||
name: "TextAreaInput",
|
||||
components: { InputWrapper },
|
||||
components: {InputWrapper},
|
||||
mixins: [],
|
||||
|
||||
props: {
|
||||
...inputProps,
|
||||
maxCharLimit: { type: Number, required: false, default: null },
|
||||
showCharLimit: { type: Boolean, required: false, default: false },
|
||||
maxCharLimit: {type: Number, required: false, default: null},
|
||||
showCharLimit: {type: Boolean, required: false, default: false},
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@
|
|||
:style="inputStyle"
|
||||
:class="[
|
||||
theme.default.input,
|
||||
theme.default.borderRadius,
|
||||
theme.default.spacing.horizontal,
|
||||
theme.default.spacing.vertical,
|
||||
theme.default.fontSize,
|
||||
{
|
||||
'!ring-red-500 !ring-2 !border-transparent': hasError,
|
||||
'!cursor-not-allowed !bg-gray-200': disabled,
|
||||
|
|
@ -55,23 +59,23 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { inputProps, useFormInput } from "./useFormInput.js"
|
||||
import {inputProps, useFormInput} from "./useFormInput.js"
|
||||
import InputWrapper from "./components/InputWrapper.vue"
|
||||
|
||||
export default {
|
||||
name: "TextInput",
|
||||
components: { InputWrapper },
|
||||
components: {InputWrapper},
|
||||
|
||||
props: {
|
||||
...inputProps,
|
||||
nativeType: { type: String, default: "text" },
|
||||
accept: { type: String, default: null },
|
||||
min: { type: Number, required: false, default: null },
|
||||
max: { type: Number, required: false, default: null },
|
||||
autocomplete: { type:[Boolean, String, Object], default: null },
|
||||
maxCharLimit: { type: Number, required: false, default: null },
|
||||
showCharLimit: { type: Boolean, required: false, default: false },
|
||||
pattern: { type: String, default: null },
|
||||
nativeType: {type: String, default: "text"},
|
||||
accept: {type: String, default: null},
|
||||
min: {type: Number, required: false, default: null},
|
||||
max: {type: Number, required: false, default: null},
|
||||
autocomplete: {type: [Boolean, String, Object], default: null},
|
||||
maxCharLimit: {type: Number, required: false, default: null},
|
||||
showCharLimit: {type: Boolean, required: false, default: false},
|
||||
pattern: {type: String, default: null},
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
|
|
@ -92,10 +96,12 @@ export default {
|
|||
...useFormInput(
|
||||
props,
|
||||
context,
|
||||
props.nativeType === "file" ? "file-" : null,
|
||||
{
|
||||
formPrefixKey: props.nativeType === "file" ? "file-" : null
|
||||
},
|
||||
),
|
||||
onEnterPress,
|
||||
onChange,
|
||||
onChange
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -4,25 +4,43 @@
|
|||
<span />
|
||||
</template>
|
||||
|
||||
<div class="flex">
|
||||
<div class="flex space-x-2 items-center">
|
||||
<v-switch
|
||||
:id="id ? id : name"
|
||||
v-model="compVal"
|
||||
class="inline-block mr-2"
|
||||
:disabled="disabled ? true : null"
|
||||
:color="color"
|
||||
:theme="theme"
|
||||
/>
|
||||
<slot name="label">
|
||||
<span>{{ label }}
|
||||
<span
|
||||
v-if="required"
|
||||
class="text-red-500 required-dot"
|
||||
>*</span></span>
|
||||
</slot>
|
||||
<div>
|
||||
<slot name="label">
|
||||
<label
|
||||
:aria-label="id ? id : name"
|
||||
:for="id ? id : name"
|
||||
:class="theme.default.fontSize"
|
||||
>
|
||||
{{ label }}
|
||||
<span
|
||||
v-if="required"
|
||||
class="text-red-500 required-dot"
|
||||
>*</span>
|
||||
</label>
|
||||
</slot>
|
||||
<slot name="help">
|
||||
<InputHelp
|
||||
:help="help"
|
||||
:help-classes="theme.default.help"
|
||||
>
|
||||
<template #after-help>
|
||||
<slot name="bottom_after_help" />
|
||||
</template>
|
||||
</InputHelp>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #help>
|
||||
<slot name="help" />
|
||||
<span class="hidden" />
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
|
|
@ -32,13 +50,15 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { inputProps, useFormInput } from "./useFormInput.js"
|
||||
import {inputProps, useFormInput} from "./useFormInput.js"
|
||||
import VSwitch from "./components/VSwitch.vue"
|
||||
import InputWrapper from "./components/InputWrapper.vue"
|
||||
import InputHelp from "~/components/forms/components/InputHelp.vue"
|
||||
|
||||
export default {
|
||||
name: "ToggleSwitchInput",
|
||||
|
||||
components: { InputWrapper, VSwitch },
|
||||
components: {InputHelp, InputWrapper, VSwitch},
|
||||
props: {
|
||||
...inputProps,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<form>
|
||||
<slot />
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* Used to pass props to children input components
|
||||
*/
|
||||
import ThemeBuilder from "~/lib/forms/themes/ThemeBuilder.js"
|
||||
|
||||
const props = defineProps({
|
||||
themeName: { type: String, default: 'default' },
|
||||
size: { type: String, default: "md" },
|
||||
})
|
||||
|
||||
const theme = computed(() => (new ThemeBuilder(props.themeName, {size: props.size})).getAllComponents())
|
||||
provide('theme', theme)
|
||||
</script>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
id="webcam"
|
||||
autoplay
|
||||
playsinline
|
||||
:class="[{ hidden: !isCapturing }, theme.fileInput.cameraInput]"
|
||||
:class="[{ hidden: !isCapturing }, theme.fileInput.minHeight, theme.fileInput.borderRadius]"
|
||||
width="1280"
|
||||
height="720"
|
||||
/>
|
||||
|
|
@ -136,7 +136,7 @@
|
|||
|
||||
<script>
|
||||
import Webcam from "webcam-easy"
|
||||
import { themes } from "~/lib/forms/form-themes.js"
|
||||
import { themes } from "~/lib/forms/themes/form-themes.js"
|
||||
export default {
|
||||
name: "FileInput",
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div
|
||||
:class="wrapperClass"
|
||||
:class="[ twMerge(theme.default.wrapper,wrapperClass)]"
|
||||
:style="inputStyle"
|
||||
>
|
||||
<slot name="label">
|
||||
|
|
@ -54,20 +54,21 @@
|
|||
<script setup>
|
||||
import InputLabel from "./InputLabel.vue"
|
||||
import InputHelp from "./InputHelp.vue"
|
||||
import {twMerge} from "tailwind-merge"
|
||||
|
||||
defineProps({
|
||||
id: { type: String, required: false },
|
||||
name: { type: String, required: false },
|
||||
label: { type: String, required: false },
|
||||
form: { type: Object, required: false },
|
||||
theme: { type: Object, required: true },
|
||||
wrapperClass: { type: String, required: false },
|
||||
inputStyle: { type: Object, required: false },
|
||||
help: { type: String, required: false },
|
||||
helpPosition: { type: String, default: "below_input" },
|
||||
uppercaseLabels: { type: Boolean, default: true },
|
||||
hideFieldName: { type: Boolean, default: true },
|
||||
required: { type: Boolean, default: false },
|
||||
hasValidation: { type: Boolean, default: true },
|
||||
id: {type: String, required: false},
|
||||
name: {type: String, required: false},
|
||||
label: {type: String, required: false},
|
||||
form: {type: Object, required: false},
|
||||
theme: {type: Object, required: true},
|
||||
wrapperClass: {type: String, required: false},
|
||||
inputStyle: {type: Object, required: false},
|
||||
help: {type: String, required: false},
|
||||
helpPosition: {type: String, default: "below_input"},
|
||||
uppercaseLabels: {type: Boolean, default: true},
|
||||
hideFieldName: {type: Boolean, default: true},
|
||||
required: {type: Boolean, default: false},
|
||||
hasValidation: {type: Boolean, default: true},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
v-model="internalValue"
|
||||
:name="name"
|
||||
type="checkbox"
|
||||
:class="sizeClasses"
|
||||
class="rounded border-gray-500 cursor-pointer checkbox"
|
||||
class="rounded border-gray-500 w-10 h-10 cursor-pointer checkbox"
|
||||
:class="theme.CheckboxInput.size"
|
||||
:style="{ '--accent-color': color }"
|
||||
:disabled="disabled ? true : null"
|
||||
>
|
||||
|
|
@ -32,7 +32,7 @@ const props = defineProps({
|
|||
name: { type: String, default: "checkbox" },
|
||||
modelValue: { type: [Boolean, String], default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
sizeClasses: { type: String, default: "w-4 h-4" },
|
||||
theme: { type: Object },
|
||||
color: { type: String, default: null },
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@
|
|||
<div
|
||||
class="inline-block w-full flex overflow-hidden"
|
||||
:style="inputStyle"
|
||||
:class="[theme.SelectInput.input, { '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200': disabled }, inputClass]"
|
||||
:class="[
|
||||
theme.SelectInput.input,
|
||||
theme.SelectInput.borderRadius,
|
||||
{ '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed !bg-gray-200': disabled },
|
||||
inputClass
|
||||
]"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -15,10 +20,18 @@
|
|||
aria-expanded="true"
|
||||
aria-labelledby="listbox-label"
|
||||
class="cursor-pointer w-full flex-grow relative"
|
||||
:class="[{'py-2': !multiple || loading, 'py-1': multiple},theme.default.inputSpacing.horizontal]"
|
||||
:class="[
|
||||
theme.SelectInput.spacing.horizontal,
|
||||
theme.SelectInput.spacing.vertical
|
||||
]"
|
||||
@click="toggleDropdown"
|
||||
>
|
||||
<div :class="{ 'h-6': !multiple, 'min-h-8': multiple && !loading }">
|
||||
<div
|
||||
class="flex items-center"
|
||||
:class="[
|
||||
theme.SelectInput.minHeight
|
||||
]"
|
||||
>
|
||||
<transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
|
|
@ -32,7 +45,6 @@
|
|||
v-else-if="modelValue"
|
||||
key="value"
|
||||
class="flex"
|
||||
:class="{ 'min-h-8': multiple }"
|
||||
>
|
||||
<slot
|
||||
name="selected"
|
||||
|
|
@ -47,7 +59,10 @@
|
|||
<slot name="placeholder">
|
||||
<div
|
||||
class="text-gray-400 dark:text-gray-500 w-full text-left truncate pr-3"
|
||||
:class="{ 'py-1': multiple && !loading }"
|
||||
:class="[
|
||||
{ 'py-1': multiple && !loading },
|
||||
theme.SelectInput.fontSize
|
||||
]"
|
||||
>
|
||||
{{ placeholder }}
|
||||
</div>
|
||||
|
|
@ -65,7 +80,7 @@
|
|||
<button
|
||||
v-if="clearable && !isEmpty"
|
||||
class="hover:bg-gray-50 dark:hover:bg-gray-900 border-l px-2"
|
||||
:class="[theme.default.inputSpacing.vertical]"
|
||||
:class="[theme.SelectInput.spacing.vertical]"
|
||||
@click.prevent="clear()"
|
||||
>
|
||||
<Icon
|
||||
|
|
@ -78,15 +93,18 @@
|
|||
</div>
|
||||
<collapsible
|
||||
v-model="isOpen"
|
||||
class="absolute mt-1 bg-white overflow-auto dark:bg-notion-dark-light shadow-xl z-10"
|
||||
:class="[dropdownClass,theme.SelectInput.dropdown]"
|
||||
class="absolute mt-1 bg-white overflow-auto dark:bg-notion-dark-light shadow-xl z-30"
|
||||
:class="[dropdownClass,theme.SelectInput.dropdown, theme.SelectInput.borderRadius]"
|
||||
@click-away="onClickAway"
|
||||
>
|
||||
<ul
|
||||
tabindex="-1"
|
||||
role="listbox"
|
||||
class="text-base leading-6 shadow-xs overflow-auto focus:outline-none sm:text-sm sm:leading-5 relative"
|
||||
:class="{ 'max-h-42': !isSearchable, 'max-h-48': isSearchable }"
|
||||
class="leading-6 shadow-xs overflow-auto focus:outline-none sm:text-sm sm:leading-5 relative"
|
||||
:class="[
|
||||
{ 'max-h-42': !isSearchable, 'max-h-48': isSearchable },
|
||||
theme.SelectInput.fontSize
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-if="isSearchable"
|
||||
|
|
@ -95,15 +113,29 @@
|
|||
<input
|
||||
v-model="searchTerm"
|
||||
type="text"
|
||||
class="flex-grow pl-3 pr-7 py-3 w-full focus:outline-none dark:text-white"
|
||||
class="flex-grow pl-3 pr-7 py-2 w-full focus:outline-none dark:text-white"
|
||||
placeholder="Search"
|
||||
>
|
||||
<div class="flex absolute right-0 inset-y-0 items-center px-2 justify-center pointer-events-none">
|
||||
<div
|
||||
v-if="!searchTerm"
|
||||
class="flex absolute right-0 inset-y-0 items-center px-2 justify-center pointer-events-none"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:magnifying-glass-solid"
|
||||
class="h-5 w-5 text-gray-500 dark:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
role="button"
|
||||
class="flex absolute right-0 inset-y-0 items-center px-2 justify-center"
|
||||
@click="searchTerm = ''"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:backspace"
|
||||
class="h-5 w-5 text-gray-500 dark:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="loading"
|
||||
|
|
@ -120,8 +152,14 @@
|
|||
:key="item[optionKey]"
|
||||
role="option"
|
||||
:style="optionStyle"
|
||||
:class="[{ 'px-3 pr-9': multiple, 'px-3': !multiple },dropdownClass,theme.SelectInput.option]"
|
||||
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:bg-gray-100 dark:hover:bg-gray-900 rounded focus:outline-none"
|
||||
:class="[
|
||||
dropdownClass,
|
||||
theme.SelectInput.option,
|
||||
theme.SelectInput.spacing.horizontal,
|
||||
theme.SelectInput.spacing.vertical,
|
||||
{ 'pr-9': multiple},
|
||||
]"
|
||||
class="text-gray-900 select-none relative cursor-pointer group hover:bg-gray-100 dark:hover:bg-gray-900 rounded focus:outline-none"
|
||||
@click="select(item)"
|
||||
>
|
||||
<slot
|
||||
|
|
@ -145,10 +183,11 @@
|
|||
role="option"
|
||||
:style="optionStyle"
|
||||
:class="[{ 'px-3 pr-9': multiple, 'px-3': !multiple },dropdownClass,theme.SelectInput.option]"
|
||||
class="text-gray-900 cursor-default select-none relative py-2 cursor-pointer group hover:bg-gray-100 dark:hover:bg-gray-900 rounded focus:outline-none"
|
||||
class="text-gray-900 select-none relative py-2 cursor-pointer group hover:bg-gray-100 dark:hover:bg-gray-900 rounded focus:outline-none"
|
||||
@click="createOption(searchTerm)"
|
||||
>
|
||||
Create <span class="px-2 bg-gray-100 border border-gray-300 rounded group-hover-text-black">{{ searchTerm
|
||||
Create <span class="px-2 bg-gray-100 border border-gray-300 rounded group-hover-text-black">{{
|
||||
searchTerm
|
||||
}}</span>
|
||||
</li>
|
||||
</div>
|
||||
|
|
@ -159,38 +198,38 @@
|
|||
|
||||
<script>
|
||||
import Collapsible from '~/components/global/transitions/Collapsible.vue'
|
||||
import { themes } from '../../../lib/forms/form-themes.js'
|
||||
import {themes} from '../../../lib/forms/themes/form-themes.js'
|
||||
import debounce from 'debounce'
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
export default {
|
||||
name: 'VSelect',
|
||||
components: { Collapsible },
|
||||
components: {Collapsible},
|
||||
directives: {},
|
||||
props: {
|
||||
data: Array,
|
||||
modelValue: { default: null, type: [String, Number, Array, Object] },
|
||||
inputClass: { type: String, default: null },
|
||||
dropdownClass: { type: String, default: 'w-full' },
|
||||
loading: { type: Boolean, default: false },
|
||||
required: { type: Boolean, default: false },
|
||||
multiple: { type: Boolean, default: false },
|
||||
searchable: { type: Boolean, default: false },
|
||||
clearable: { type: Boolean, default: false },
|
||||
hasError: { type: Boolean, default: false },
|
||||
remote: { type: Function, default: null },
|
||||
searchKeys: { type: Array, default: () => ['name'] },
|
||||
optionKey: { type: String, default: 'id' },
|
||||
emitKey: { type: String, default: null },
|
||||
color: { type: String, default: '#3B82F6' },
|
||||
placeholder: { type: String, default: null },
|
||||
uppercaseLabels: { type: Boolean, default: true },
|
||||
theme: { type: Object, default: () => themes.default },
|
||||
allowCreation: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false }
|
||||
modelValue: {default: null, type: [String, Number, Array, Object]},
|
||||
inputClass: {type: String, default: null},
|
||||
dropdownClass: {type: String, default: 'w-full'},
|
||||
loading: {type: Boolean, default: false},
|
||||
required: {type: Boolean, default: false},
|
||||
multiple: {type: Boolean, default: false},
|
||||
searchable: {type: Boolean, default: false},
|
||||
clearable: {type: Boolean, default: false},
|
||||
hasError: {type: Boolean, default: false},
|
||||
remote: {type: Function, default: null},
|
||||
searchKeys: {type: Array, default: () => ['name']},
|
||||
optionKey: {type: String, default: 'id'},
|
||||
emitKey: {type: String, default: null},
|
||||
color: {type: String, default: '#3B82F6'},
|
||||
placeholder: {type: String, default: null},
|
||||
uppercaseLabels: {type: Boolean, default: true},
|
||||
theme: {type: Object, default: () => themes.default},
|
||||
allowCreation: {type: Boolean, default: false},
|
||||
disabled: {type: Boolean, default: false}
|
||||
},
|
||||
emits: ['update:modelValue', 'update-options'],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
isOpen: false,
|
||||
searchTerm: '',
|
||||
|
|
@ -198,46 +237,46 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
optionStyle () {
|
||||
optionStyle() {
|
||||
return {
|
||||
'--bg-form-color': this.color
|
||||
}
|
||||
},
|
||||
inputStyle () {
|
||||
inputStyle() {
|
||||
return {
|
||||
'--tw-ring-color': this.color
|
||||
}
|
||||
},
|
||||
debouncedRemote () {
|
||||
debouncedRemote() {
|
||||
if (this.remote) {
|
||||
return debounce(this.remote, 300)
|
||||
}
|
||||
return null
|
||||
},
|
||||
filteredOptions () {
|
||||
filteredOptions() {
|
||||
if (!this.data) return []
|
||||
if (!this.searchable || this.remote || this.searchTerm === '') {
|
||||
return this.data
|
||||
}
|
||||
|
||||
// Fuse search
|
||||
const fuzeOptions = {
|
||||
keys: this.searchKeys
|
||||
}
|
||||
const fuse = new Fuse(this.data, fuzeOptions)
|
||||
return fuse.search(this.searchTerm).map((res) => {
|
||||
const fuse = new Fuse(this.data, {
|
||||
keys: this.searchKeys,
|
||||
includeScore: true
|
||||
})
|
||||
return fuse.search(this.searchTerm).filter((res) => res.score < 0.5).map((res) => {
|
||||
return res.item
|
||||
})
|
||||
},
|
||||
isSearchable () {
|
||||
isSearchable() {
|
||||
return this.searchable || this.remote !== null || this.allowCreation
|
||||
},
|
||||
isEmpty () {
|
||||
isEmpty() {
|
||||
return this.multiple ? !this.modelValue || this.modelValue.length === 0 : !this.modelValue
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searchTerm (val) {
|
||||
searchTerm(val) {
|
||||
if (!this.debouncedRemote) return
|
||||
if ((this.remote && val) || (val === '' && !this.modelValue) || (val === '' && this.isOpen)) {
|
||||
return this.debouncedRemote(val)
|
||||
|
|
@ -245,13 +284,13 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
onClickAway (event) {
|
||||
onClickAway(event) {
|
||||
// Check that event target isn't children of dropdown
|
||||
if (this.$refs.select && !this.$refs.select.contains(event.target)) {
|
||||
this.isOpen = false
|
||||
}
|
||||
},
|
||||
isSelected (value) {
|
||||
isSelected(value) {
|
||||
if (!this.modelValue) return false
|
||||
|
||||
if (this.emitKey && value[this.emitKey]) {
|
||||
|
|
@ -263,7 +302,7 @@ export default {
|
|||
}
|
||||
return this.modelValue === value
|
||||
},
|
||||
toggleDropdown () {
|
||||
toggleDropdown() {
|
||||
if (this.disabled) {
|
||||
this.isOpen = false
|
||||
} else {
|
||||
|
|
@ -273,7 +312,7 @@ export default {
|
|||
this.searchTerm = ''
|
||||
}
|
||||
},
|
||||
select (value) {
|
||||
select(value) {
|
||||
if (!this.multiple) {
|
||||
// Close after select
|
||||
this.toggleDropdown()
|
||||
|
|
@ -306,10 +345,10 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
clear () {
|
||||
clear() {
|
||||
this.$emit('update:modelValue', this.multiple ? [] : null)
|
||||
},
|
||||
createOption (newOption) {
|
||||
createOption(newOption) {
|
||||
if (newOption) {
|
||||
const newItem = {
|
||||
name: newOption,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
<template>
|
||||
<div
|
||||
role="button"
|
||||
:id="id || name"
|
||||
:aria-labelledby="id || name"
|
||||
role="checkbox"
|
||||
:aria-checked="props.modelValue"
|
||||
class="flex"
|
||||
@click.stop="onClick"
|
||||
>
|
||||
<div
|
||||
class="inline-flex items-center h-6 w-12 p-1 bg-gray-300 border rounded-full cursor-pointer focus:outline-none transition-all transform ease-in-out duration-100"
|
||||
:class="{ 'toggle-switch': props.modelValue }"
|
||||
class="inline-flex items-center bg-gray-300 rounded-full cursor-pointer focus:outline-none transition-all transform ease-in-out duration-100"
|
||||
:class="[{ 'toggle-switch': props.modelValue }, theme.SwitchInput.containerSize]"
|
||||
:style="{ '--accent-color': props.color }"
|
||||
|
||||
>
|
||||
<div
|
||||
class="inline-block h-4 w-4 rounded-full bg-white shadow transition-all transform ease-in-out duration-150 rounded-2xl scale-100"
|
||||
:class="{ 'translate-x-5.5': props.modelValue }"
|
||||
class="inline-block h-4 w-4 rounded-full bg-white transition-all transform ease-in-out duration-150 scale-100"
|
||||
:class="{ [theme.SwitchInput.translatedClass]: props.modelValue}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -21,9 +24,12 @@
|
|||
import { defineEmits, defineProps } from "vue"
|
||||
|
||||
const props = defineProps({
|
||||
id: { type: String, default: null },
|
||||
name: { type: String, default: "checkbox" },
|
||||
modelValue: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
color: { type: String, default: '#3B82F6' },
|
||||
theme: { type: Object },
|
||||
})
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,41 @@
|
|||
import { ref, computed, watch } from "vue"
|
||||
import { themes } from "~/lib/forms/form-themes.js"
|
||||
import { default as _get } from "lodash/get"
|
||||
import { default as _set } from "lodash/set"
|
||||
import { default as _has } from "lodash/has"
|
||||
import {ref, computed, watch} from "vue"
|
||||
import {default as _get} from "lodash/get"
|
||||
import {default as _set} from "lodash/set"
|
||||
import {default as _has} from "lodash/has"
|
||||
import CachedDefaultTheme from "~/lib/forms/themes/CachedDefaultTheme.js"
|
||||
|
||||
export const inputProps = {
|
||||
id: { type: String, default: null },
|
||||
name: { type: String, required: true },
|
||||
label: { type: String, required: false },
|
||||
form: { type: Object, required: false },
|
||||
theme: { type: Object, default: () => themes.default },
|
||||
modelValue: { required: false },
|
||||
required: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
placeholder: { type: String, default: null },
|
||||
uppercaseLabels: { type: Boolean, default: false },
|
||||
hideFieldName: { type: Boolean, default: false },
|
||||
showCharLimit: { type: Boolean, default: false },
|
||||
help: { type: String, default: null },
|
||||
helpPosition: { type: String, default: "below_input" },
|
||||
color: { type: String, default: "#3B82F6" },
|
||||
wrapperClass: { type: String, default: "relative mb-3" },
|
||||
id: {type: String, default: null},
|
||||
name: {type: String, required: true},
|
||||
label: {type: String, required: false},
|
||||
form: {type: Object, required: false},
|
||||
theme: {
|
||||
type: Object, default: () => {
|
||||
const theme = inject("theme", null)
|
||||
if (theme) {
|
||||
return theme.value
|
||||
}
|
||||
return CachedDefaultTheme.getInstance()
|
||||
}
|
||||
},
|
||||
modelValue: {required: false},
|
||||
required: {type: Boolean, default: false},
|
||||
disabled: {type: Boolean, default: false},
|
||||
placeholder: {type: String, default: null},
|
||||
uppercaseLabels: {type: Boolean, default: false},
|
||||
hideFieldName: {type: Boolean, default: false},
|
||||
showCharLimit: {type: Boolean, default: false},
|
||||
help: {type: String, default: null},
|
||||
helpPosition: {type: String, default: "below_input"},
|
||||
color: {type: String, default: "#3B82F6"},
|
||||
wrapperClass: {type: String, default: ""},
|
||||
}
|
||||
|
||||
export function useFormInput(props, context, formPrefixKey = null) {
|
||||
export function useFormInput(props, context, options = {}) {
|
||||
const composableOptions = {
|
||||
formPrefixKey: null,
|
||||
...options
|
||||
}
|
||||
const content = ref(props.modelValue)
|
||||
|
||||
const inputStyle = computed(() => {
|
||||
|
|
@ -47,13 +59,13 @@ export function useFormInput(props, context, formPrefixKey = null) {
|
|||
const compVal = computed({
|
||||
get: () => {
|
||||
if (props.form) {
|
||||
return _get(props.form, (formPrefixKey || "") + props.name)
|
||||
return _get(props.form, (composableOptions.formPrefixKey || "") + props.name)
|
||||
}
|
||||
return content.value
|
||||
},
|
||||
set: (val) => {
|
||||
if (props.form) {
|
||||
_set(props.form, (formPrefixKey || "") + props.name, val)
|
||||
_set(props.form, (composableOptions.formPrefixKey || "") + props.name, val)
|
||||
} else {
|
||||
content.value = val
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
<script setup>
|
||||
import { ref, defineProps, defineEmits } from "vue"
|
||||
import OpenForm from "../forms/OpenForm.vue"
|
||||
import { themes } from "~/lib/forms/form-themes.js"
|
||||
import { themes } from "~/lib/forms/themes/form-themes.js"
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, required: true },
|
||||
form: { type: Object, required: true },
|
||||
|
|
|
|||
|
|
@ -193,13 +193,12 @@
|
|||
<script>
|
||||
import OpenForm from './OpenForm.vue'
|
||||
import OpenFormButton from './OpenFormButton.vue'
|
||||
import { themes } from '~/lib/forms/form-themes.js'
|
||||
import VButton from '~/components/global/VButton.vue'
|
||||
import FormCleanings from '../../pages/forms/show/FormCleanings.vue'
|
||||
import VTransition from '~/components/global/transitions/VTransition.vue'
|
||||
import {pendingSubmission} from "~/composables/forms/pendingSubmission.js"
|
||||
import clonedeep from "clone-deep"
|
||||
import { default as _has } from 'lodash/has'
|
||||
import ThemeBuilder from "~/lib/forms/themes/ThemeBuilder.js"
|
||||
|
||||
export default {
|
||||
components: { VTransition, VButton, OpenFormButton, OpenForm, FormCleanings },
|
||||
|
|
@ -227,7 +226,6 @@ export default {
|
|||
return {
|
||||
loading: false,
|
||||
submitted: false,
|
||||
themes: themes,
|
||||
passwordForm: useForm({
|
||||
password: null
|
||||
}),
|
||||
|
|
@ -241,7 +239,10 @@ export default {
|
|||
return import.meta.client && window.location.href.includes('popup=true')
|
||||
},
|
||||
theme () {
|
||||
return this.themes[_has(this.themes, this.form.theme) ? this.form.theme : 'default']
|
||||
return new ThemeBuilder(this.form.theme, {
|
||||
size: this.form.size,
|
||||
borderRadius: this.form.border_radius
|
||||
}).getAllComponents()
|
||||
},
|
||||
isPublicFormPage () {
|
||||
return this.$route.name === 'forms-slug'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<button
|
||||
:type="nativeType"
|
||||
:disabled="loading ? true : null"
|
||||
:class="`py-${sizes['p-y']} px-${sizes['p-x']} text-${sizes['font']} ${theme.Button.body}`"
|
||||
:class="[`py-${sizes['p-y']} px-${sizes['p-x']} text-${sizes['font']}`,theme.Button.body,theme.Button.borderRadius]"
|
||||
:style="buttonStyle"
|
||||
class="btn"
|
||||
>
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { themes } from "~/lib/forms/form-themes.js"
|
||||
import {themes} from "~/lib/forms/themes/form-themes.js"
|
||||
|
||||
export default {
|
||||
name: "OpenFormButton",
|
||||
|
|
@ -43,7 +43,7 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
|
||||
theme: { type: Object, default: () => themes.default },
|
||||
theme: {type: Object, default: () => themes.default},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -91,14 +91,19 @@
|
|||
allow you to preview your form changes.
|
||||
</div>
|
||||
|
||||
<form-information />
|
||||
<form-structure />
|
||||
<form-customization />
|
||||
<form-about-submission />
|
||||
<form-access />
|
||||
<form-security-privacy />
|
||||
<form-custom-seo />
|
||||
<form-custom-code />
|
||||
<VForm
|
||||
size="sm"
|
||||
@submit.prevent=""
|
||||
>
|
||||
<form-information />
|
||||
<form-structure />
|
||||
<form-customization />
|
||||
<form-about-submission />
|
||||
<form-access />
|
||||
<form-security-privacy />
|
||||
<form-custom-seo />
|
||||
<form-custom-code />
|
||||
</VForm>
|
||||
</div>
|
||||
|
||||
<form-editor-preview />
|
||||
|
|
|
|||
|
|
@ -55,9 +55,11 @@
|
|||
<p class="flex-grow truncate">
|
||||
{{ field.name }}
|
||||
</p>
|
||||
<v-switch
|
||||
<ToggleSwitchInput
|
||||
v-model="displayColumns[field.id]"
|
||||
class="float-right"
|
||||
wrapper-class="mb-0"
|
||||
label=""
|
||||
name="field.id"
|
||||
@update:model-value="onChangeDisplayColumns"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -77,9 +79,11 @@
|
|||
<p class="flex-grow truncate">
|
||||
{{ field.name }}
|
||||
</p>
|
||||
<v-switch
|
||||
<ToggleSwitchInput
|
||||
v-model="displayColumns[field.id]"
|
||||
class="float-right"
|
||||
wrapper-class="mb-0"
|
||||
label=""
|
||||
name="field.id"
|
||||
@update:model-value="onChangeDisplayColumns"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -98,12 +102,14 @@
|
|||
class="flex flex-wrap items-end"
|
||||
>
|
||||
<div class="flex-grow">
|
||||
<text-input
|
||||
class="w-64"
|
||||
:form="searchForm"
|
||||
name="search"
|
||||
placeholder="Search..."
|
||||
/>
|
||||
<VForm size="sm">
|
||||
<text-input
|
||||
class="w-64"
|
||||
:form="searchForm"
|
||||
name="search"
|
||||
placeholder="Search..."
|
||||
/>
|
||||
</VForm>
|
||||
</div>
|
||||
<div class="font-semibold flex gap-4">
|
||||
<p class="float-right text-xs uppercase mb-2">
|
||||
|
|
|
|||
|
|
@ -35,8 +35,10 @@
|
|||
help="Gives user a unique url to update their submission"
|
||||
>
|
||||
<template #label>
|
||||
Editable submissions
|
||||
<pro-tag class="ml-1" />
|
||||
<span class="text-sm">
|
||||
Editable submissions
|
||||
</span>
|
||||
<pro-tag class="-mt-1 ml-1" />
|
||||
</template>
|
||||
</toggle-switch-input>
|
||||
<text-input
|
||||
|
|
@ -138,7 +140,7 @@
|
|||
help="Show a message, or redirect to a URL"
|
||||
>
|
||||
<template #selected="{ option, optionName }">
|
||||
<div class="flex items-center truncate mr-6">
|
||||
<div class="flex items-center text-sm truncate mr-6">
|
||||
{{ optionName }}
|
||||
<pro-tag
|
||||
v-if="option === 'redirect'"
|
||||
|
|
@ -147,8 +149,8 @@
|
|||
</div>
|
||||
</template>
|
||||
<template #option="{ option, selected }">
|
||||
<span class="flex hover:text-white">
|
||||
<p class="flex-grow hover:text-white">
|
||||
<span class="flex">
|
||||
<p class="flex-grow">
|
||||
{{ option.name }}
|
||||
<template v-if="option.value === 'redirect'"><pro-tag /></template>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -53,9 +53,34 @@
|
|||
label="Form Theme"
|
||||
/>
|
||||
|
||||
<div class="flex space-x-4 justify-stretch">
|
||||
<select-input
|
||||
name="size"
|
||||
class="flex-grow"
|
||||
:options="[
|
||||
{ name: 'Small', value: 'sm' },
|
||||
{ name: 'Medium', value: 'md' },
|
||||
{ name: 'Large', value: 'lg' },
|
||||
]"
|
||||
:form="form"
|
||||
label="Input Size"
|
||||
/>
|
||||
|
||||
<select-input
|
||||
name="border_radius"
|
||||
class="flex-grow"
|
||||
:options="[
|
||||
{ name: 'None', value: 'none' },
|
||||
{ name: 'Small', value: 'small' },
|
||||
{ name: 'Full', value: 'full' },
|
||||
]"
|
||||
:form="form"
|
||||
label="Input Roundness"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<select-input
|
||||
name="dark_mode"
|
||||
class="mt-4"
|
||||
help="To see changes, save your form and open it"
|
||||
:options="[
|
||||
{ name: 'Auto - use Device System Preferences', value: 'auto' },
|
||||
|
|
@ -68,7 +93,6 @@
|
|||
|
||||
<select-input
|
||||
name="width"
|
||||
class="mt-4"
|
||||
:options="[
|
||||
{ name: 'Centered', value: 'centered' },
|
||||
{ name: 'Full Width', value: 'full' },
|
||||
|
|
@ -80,7 +104,6 @@
|
|||
|
||||
<image-input
|
||||
name="cover_picture"
|
||||
class="mt-4"
|
||||
:form="form"
|
||||
label="Cover Picture"
|
||||
help="Not visible when form is embedded"
|
||||
|
|
@ -89,7 +112,6 @@
|
|||
|
||||
<image-input
|
||||
name="logo_picture"
|
||||
class="mt-4"
|
||||
:form="form"
|
||||
label="Logo"
|
||||
help="Not visible when form is embedded"
|
||||
|
|
@ -98,49 +120,44 @@
|
|||
|
||||
<color-input
|
||||
name="color"
|
||||
class="mt-4"
|
||||
:form="form"
|
||||
label="Color (for buttons & inputs border)"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
name="hide_title"
|
||||
:form="form"
|
||||
class="mt-4"
|
||||
label="Hide Title"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
name="no_branding"
|
||||
:form="form"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #label>
|
||||
Remove OpnForm Branding
|
||||
<pro-tag class="ml-1" />
|
||||
<span class="text-sm">
|
||||
Remove OpnForm Branding
|
||||
</span>
|
||||
<pro-tag class="-mt-1" />
|
||||
</template>
|
||||
</toggle-switch-input>
|
||||
<toggle-switch-input
|
||||
name="show_progress_bar"
|
||||
:form="form"
|
||||
class="mt-4"
|
||||
label="Show progress bar"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
name="uppercase_labels"
|
||||
:form="form"
|
||||
class="mt-4"
|
||||
label="Uppercase Input Labels"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
name="transparent_background"
|
||||
:form="form"
|
||||
class="mt-4"
|
||||
label="Transparent Background"
|
||||
help="Only applies when form is embedded"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
name="confetti_on_submission"
|
||||
:form="form"
|
||||
class="mt-4"
|
||||
label="Confetti on successful submisison"
|
||||
@update:model-value="onChangeConfettiOnSubmission"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2,17 +2,22 @@
|
|||
<editor-right-sidebar
|
||||
:show="form && (showEditFieldSidebar || showAddFieldSidebar)"
|
||||
>
|
||||
<transition mode="out-in">
|
||||
<form-field-edit
|
||||
v-if="showEditFieldSidebar"
|
||||
:key="editFieldIndex"
|
||||
v-motion-fade="'fade'"
|
||||
/>
|
||||
<add-form-block
|
||||
v-else-if="showAddFieldSidebar"
|
||||
v-motion-fade="'fade'"
|
||||
/>
|
||||
</transition>
|
||||
<VForm
|
||||
size="sm"
|
||||
@submit.prevent=""
|
||||
>
|
||||
<transition mode="out-in">
|
||||
<form-field-edit
|
||||
v-if="showEditFieldSidebar"
|
||||
:key="editFieldIndex"
|
||||
v-motion-fade="'fade'"
|
||||
/>
|
||||
<add-form-block
|
||||
v-else-if="showAddFieldSidebar"
|
||||
v-motion-fade="'fade'"
|
||||
/>
|
||||
</transition>
|
||||
</VForm>
|
||||
</editor-right-sidebar>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@
|
|||
name="tags"
|
||||
label="Tags"
|
||||
:form="form"
|
||||
class="mt-4"
|
||||
help="To organize your forms (hidden to respondents)"
|
||||
placeholder="Select Tag(s)"
|
||||
:multiple="true"
|
||||
|
|
@ -47,7 +46,6 @@
|
|||
name="visibility"
|
||||
label="Visibility"
|
||||
:form="form"
|
||||
class="mt-4"
|
||||
help="Only public form will be accessible"
|
||||
placeholder="Select Visibility"
|
||||
:required="true"
|
||||
|
|
@ -56,7 +54,7 @@
|
|||
<v-button
|
||||
v-if="copyFormOptions.length > 0"
|
||||
color="light-gray"
|
||||
class="w-full mt-4"
|
||||
class="w-full"
|
||||
@click="showCopyFormSettingsModal = true"
|
||||
>
|
||||
<svg
|
||||
|
|
@ -98,20 +96,13 @@
|
|||
</svg>
|
||||
</template>
|
||||
<template #title>
|
||||
Copy Settings from another form
|
||||
Import Settings from another form
|
||||
</template>
|
||||
<div class="p-4 min-h-[450px]">
|
||||
<p class="text-gray-600">
|
||||
If you already have another form that you like to use as a base for
|
||||
this form, you can do that here. Select another form, confirm, and we
|
||||
will copy all of the other form settings (except the form structure)
|
||||
to this form.
|
||||
</p>
|
||||
<div>
|
||||
<select-input
|
||||
v-model="copyFormId"
|
||||
name="copy_form_id"
|
||||
label="Copy Settings From"
|
||||
class="mt-3 mb-6"
|
||||
placeholder="Choose a form"
|
||||
:searchable="copyFormOptions.length > 5"
|
||||
:options="copyFormOptions"
|
||||
|
|
|
|||
|
|
@ -31,14 +31,13 @@
|
|||
|
||||
<!-- Remember Me -->
|
||||
<div class="relative flex items-center my-5">
|
||||
<v-checkbox
|
||||
<CheckboxInput
|
||||
v-model="remember"
|
||||
class="w-full md:w-1/2"
|
||||
name="remember"
|
||||
size="small"
|
||||
>
|
||||
Remember me
|
||||
</v-checkbox>
|
||||
label="Remember me"
|
||||
/>
|
||||
|
||||
<div class="w-full md:w-1/2 text-right">
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@
|
|||
<script>
|
||||
import FormUrlPrefill from "../../../open/forms/components/FormUrlPrefill.vue"
|
||||
import OpenForm from "../../../open/forms/OpenForm.vue"
|
||||
import { themes } from "~/lib/forms/form-themes.js"
|
||||
import { themes } from "~/lib/forms/themes/form-themes.js"
|
||||
|
||||
export default {
|
||||
name: "UrlFormPrefill",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const initForm = (defaultValue = {}, withDefaultProperties = false) => {
|
|||
color: "#3B82F6",
|
||||
hide_title: false,
|
||||
no_branding: false,
|
||||
uppercase_labels: true,
|
||||
uppercase_labels: false,
|
||||
transparent_background: false,
|
||||
closes_at: null,
|
||||
closed_text:
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
/**
|
||||
Input classes for each supported form themes
|
||||
*/
|
||||
export const themes = {
|
||||
default: {
|
||||
default: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full py-2 px-4 bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
inputSpacing: {
|
||||
vertical: 'py-2',
|
||||
horizontal: 'px-4'
|
||||
},
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
Button: {
|
||||
body: 'transition ease-in duration-200 text-center font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg filter hover:brightness-110'
|
||||
},
|
||||
DateInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
CodeInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
RichTextAreaInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-1 focus:ring-opacity-100 focus:border-transparent focus:ring-2',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
SelectInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'relative w-full rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||
help: 'text-gray-400 dark:text-gray-500',
|
||||
dropdown: 'rounded-lg border border-gray-300 dark:border-gray-600',
|
||||
option: 'rounded-md'
|
||||
},
|
||||
ScaleInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
button: 'cursor-pointer text-gray-700 inline-block rounded-lg border-gray-300 px-4 py-2 flex-grow dark:bg-notion-dark-light dark:text-gray-300 text-center',
|
||||
unselectedButton: 'bg-white hover:bg-gray-50 border',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
SliderInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
stepLabel: 'text-gray-700 dark:text-gray-300 text-center text-xs',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
fileInput: {
|
||||
input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded-lg',
|
||||
cameraInput: 'min-h-40 rounded-lg',
|
||||
inputHover: {
|
||||
light: 'bg-neutral-50',
|
||||
dark: 'bg-notion-dark-light'
|
||||
},
|
||||
uploadedFile: 'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light rounded-lg shadow-sm max-w-[10rem]'
|
||||
}
|
||||
},
|
||||
simple: {
|
||||
default: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full py-2 px-2 bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
inputSpacing: {
|
||||
vertical: 'py-2',
|
||||
horizontal: 'px-4'
|
||||
},
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
Button: {
|
||||
body: 'transition ease-in duration-200 text-center font-semibold focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-110'
|
||||
},
|
||||
DateInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
SelectInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'relative w-full flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||
help: 'text-gray-400 dark:text-gray-500',
|
||||
dropdown: 'border border-gray-300 dark:border-gray-600',
|
||||
option: ''
|
||||
},
|
||||
CodeInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'border border-gray-300 dark:border-gray-600 overflow-hidden',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
RichTextAreaInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'border-transparent flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-1 focus:ring-opacity-100 focus:border-transparent focus:ring-2',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
ScaleInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
button: 'flex-1 appearance-none border-gray-300 dark:border-gray-600 w-full py-2 px-2 bg-gray-50 text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 text-center',
|
||||
unselectedButton: 'bg-white hover:bg-gray-50 border -mx-4',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
SliderInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
stepLabel: 'text-gray-700 dark:text-gray-300 text-center text-xs',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
fileInput: {
|
||||
input: 'min-h-40 border border-dashed border-gray-300 p-4',
|
||||
cameraInput: 'min-h-40',
|
||||
inputHover: {
|
||||
light: 'bg-neutral-50',
|
||||
dark: 'bg-notion-dark-light'
|
||||
},
|
||||
uploadedFile: 'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light shadow-sm max-w-[10rem]'
|
||||
}
|
||||
},
|
||||
notion: {
|
||||
default: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full py-2 px-2 bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
||||
inputSpacing: {
|
||||
vertical: 'py-2',
|
||||
horizontal: 'px-4'
|
||||
},
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
Button: {
|
||||
body: 'rounded-md transition ease-in duration-200 text-center font-semibold shadow shadow-inner-notion focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-110'
|
||||
},
|
||||
DateInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded shadow-inner-notion border-transparent focus:border-transparent flex-1 appearance-none w-full bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion p-[1px]',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
SelectInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded relative w-full border-transparent flex-1 appearance-none bg-notion-input-background shadow-inner-notion w-full px-2 text-gray-900 placeholder-gray-400 dark:bg-notion-dark-light dark:placeholder-gray-500 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
||||
help: 'text-notion-input-help dark:text-gray-500',
|
||||
dropdown: 'rounded border border-gray-300 dark:border-gray-600',
|
||||
option: 'rounded'
|
||||
},
|
||||
CodeInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded shadow-inner-notion border border-gray-300 dark:border-gray-600 overflow-hidden',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
RichTextAreaInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion border border-gray-300 dark:border-gray-600 w-full text-gray-900 bg-notion-input-background dark:bg-notion-dark-light shadow-inner dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:ring-opacity-100 focus:border-transparent focus:ring-0 focus:shadow-focus-notion',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
ScaleInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
button: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full py-2 px-2 bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 text-center',
|
||||
unselectedButton: 'bg-notion-input-background dark:bg-notion-dark-light hover:bg-gray-50 border',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
SliderInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
stepLabel: 'text-gray-700 dark:text-gray-300 text-center text-xs',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
fileInput: {
|
||||
input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded bg-notion-input-background',
|
||||
cameraInput: 'min-h-40 rounded',
|
||||
inputHover: {
|
||||
light: 'bg-neutral-50',
|
||||
dark: 'bg-notion-dark-light'
|
||||
},
|
||||
uploadedFile: 'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light rounded shadow-sm max-w-[10rem]'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import ThemeBuilder from './ThemeBuilder.js'
|
||||
|
||||
const CachedDefaultTheme = (function() {
|
||||
let instance
|
||||
|
||||
function createInstance() {
|
||||
const themeBuilder = new ThemeBuilder()
|
||||
return themeBuilder.getAllComponents()
|
||||
}
|
||||
|
||||
return {
|
||||
getInstance: function() {
|
||||
if (!instance) {
|
||||
instance = createInstance()
|
||||
}
|
||||
return instance
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
export default CachedDefaultTheme
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import {twMerge} from 'tailwind-merge'
|
||||
import {themes} from './form-themes.js'
|
||||
|
||||
export const sizes = ['sm', 'md', 'lg']
|
||||
|
||||
class ThemeBuilder {
|
||||
constructor(theme = 'default', options = {}) {
|
||||
this.theme = themes[theme] || themes.default
|
||||
this.size = options.size || 'md'
|
||||
this.borderRadius = options.borderRadius || 'small'
|
||||
}
|
||||
|
||||
extractSizedClasses(baseConfig) {
|
||||
if (typeof baseConfig === 'object' &&
|
||||
sizes.every((size) => baseConfig[size])) {
|
||||
return baseConfig[this.size]
|
||||
}
|
||||
|
||||
return baseConfig
|
||||
}
|
||||
|
||||
mergeNestedClasses(baseConfig, componentConfig) {
|
||||
const mergedConfig = {}
|
||||
|
||||
const allKeys = new Set([
|
||||
...Object.keys(baseConfig),
|
||||
...Object.keys(componentConfig),
|
||||
])
|
||||
|
||||
allKeys.forEach((key) => {
|
||||
const baseValue = this.extractSizedClasses(baseConfig[key])
|
||||
const componentValue = this.extractSizedClasses(componentConfig[key])
|
||||
|
||||
if (key === 'borderRadius') {
|
||||
// Special case for border radius
|
||||
const borderRadiusClass = baseConfig.borderRadius?.[this.borderRadius] || ''
|
||||
mergedConfig[key] = twMerge(borderRadiusClass, componentValue)
|
||||
} else if (
|
||||
typeof baseValue === 'object' &&
|
||||
baseValue !== null &&
|
||||
!Array.isArray(baseValue)) {
|
||||
mergedConfig[key] = this.mergeNestedClasses(baseValue, componentValue || {})
|
||||
} else {
|
||||
mergedConfig[key] = twMerge(baseValue || '', componentValue || '')
|
||||
}
|
||||
})
|
||||
|
||||
return mergedConfig
|
||||
}
|
||||
|
||||
getComponentTheme(componentName = 'default') {
|
||||
const baseComponentConfig = this.theme.default || {}
|
||||
const componentConfig = this.theme[componentName] || {}
|
||||
return this.mergeNestedClasses(baseComponentConfig, componentConfig)
|
||||
}
|
||||
|
||||
// Get all components classes for the selected theme
|
||||
getAllComponents() {
|
||||
const allComponents = {}
|
||||
|
||||
Object.keys(this.theme).forEach((componentName) => {
|
||||
allComponents[componentName] = this.getComponentTheme(componentName)
|
||||
})
|
||||
|
||||
return allComponents
|
||||
}
|
||||
}
|
||||
|
||||
export default ThemeBuilder
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
/**
|
||||
Input classes for each supported form themes
|
||||
*/
|
||||
export const themes = {
|
||||
default: {
|
||||
default: {
|
||||
wrapper: {
|
||||
sm: 'relative mb-2',
|
||||
md: 'relative mb-3',
|
||||
lg: 'relative mb-3',
|
||||
},
|
||||
label: 'text-gray-700 dark:text-gray-300 font-medium',
|
||||
input:
|
||||
'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
help: 'text-gray-500',
|
||||
spacing: {
|
||||
horizontal: {
|
||||
sm: 'px-2',
|
||||
md: 'px-4',
|
||||
lg: 'px-5'
|
||||
},
|
||||
vertical: {
|
||||
sm: 'py-1.5',
|
||||
md: 'py-2',
|
||||
lg: 'py-3'
|
||||
}
|
||||
},
|
||||
fontSize: {
|
||||
sm: 'text-sm',
|
||||
md: 'text-base',
|
||||
lg: 'text-lg'
|
||||
},
|
||||
borderRadius: {
|
||||
none: 'rounded-none',
|
||||
small: 'rounded-lg',
|
||||
full: 'rounded-[20px]'
|
||||
}
|
||||
},
|
||||
ScaleInput: {
|
||||
button: 'cursor-pointer text-gray-700 inline-block border-gray-300 flex-grow dark:bg-notion-dark-light dark:text-gray-300 text-center',
|
||||
unselectedButton: 'bg-white border'
|
||||
},
|
||||
SliderInput: {
|
||||
stepLabel: 'text-gray-700 dark:text-gray-300 text-center text-xs'
|
||||
},
|
||||
Button: {
|
||||
body: 'transition ease-in duration-200 text-center font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-90'
|
||||
},
|
||||
CodeInput: {
|
||||
input: 'overflow-hidden'
|
||||
},
|
||||
RichTextAreaInput: {
|
||||
input:
|
||||
'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-1 focus:ring-opacity-100 focus:border-transparent focus:ring-2'
|
||||
},
|
||||
SelectInput: {
|
||||
input:
|
||||
'relative w-full flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||
dropdown: 'border border-gray-300 dark:border-gray-600',
|
||||
option: 'rounded',
|
||||
minHeight: {
|
||||
sm: 'min-h-[20px]',
|
||||
md: 'min-h-[24px]',
|
||||
lg: 'min-h-[28px]'
|
||||
}
|
||||
},
|
||||
FlatSelectInput: {
|
||||
option: 'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900 flex items-center space-x-2 border-t first:border-t-0 px-2',
|
||||
unselectedIcon: 'text-gray-300 dark:text-gray-600',
|
||||
icon: {
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-5 h-5',
|
||||
lg: 'w-6 h-6 mx-1'
|
||||
}
|
||||
},
|
||||
DateInput: {
|
||||
input:
|
||||
'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100'
|
||||
},
|
||||
CheckboxInput:{
|
||||
size: {
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-5 h-5',
|
||||
lg: 'w-5 h-5'
|
||||
},
|
||||
},
|
||||
SwitchInput:{
|
||||
containerSize: {
|
||||
sm: 'h-5 w-10 p-0.5',
|
||||
md: 'h-6 w-12 p-1',
|
||||
lg: 'h-6 w-12 p-1'
|
||||
},
|
||||
circleSize: {
|
||||
sm: 'h-4 w-4',
|
||||
md: 'h-4 w-4',
|
||||
lg: 'h-4 w-4'
|
||||
},
|
||||
translatedClass: {
|
||||
sm: 'translate-x-5',
|
||||
md: 'translate-x-6',
|
||||
lg: 'translate-x-6'
|
||||
}
|
||||
},
|
||||
RatingInput:{
|
||||
size: {
|
||||
sm: 'w-6 h-6',
|
||||
md: 'w-8 h-8',
|
||||
lg: 'w-10 h-10'
|
||||
},
|
||||
},
|
||||
fileInput: {
|
||||
input:
|
||||
'border border-dashed border-gray-300 dark:border-gray-600 p-4 shadow-none',
|
||||
minHeight: {
|
||||
sm: 'min-h-28',
|
||||
md: 'min-h-40',
|
||||
lg: 'min-h-58'
|
||||
},
|
||||
inputHover: {
|
||||
light: 'bg-neutral-50',
|
||||
dark: 'bg-notion-dark-light'
|
||||
},
|
||||
uploadedFile:
|
||||
'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light rounded-lg shadow-sm max-w-[10rem]'
|
||||
},
|
||||
SignatureInput: {
|
||||
minHeight: {
|
||||
sm: 'min-h-28',
|
||||
md: 'min-h-40',
|
||||
lg: 'min-h-48'
|
||||
},
|
||||
}
|
||||
},
|
||||
simple: {
|
||||
default: {
|
||||
wrapper: {
|
||||
sm: 'relative mb-2',
|
||||
md: 'relative mb-3',
|
||||
lg: 'relative mb-3',
|
||||
},
|
||||
label: 'text-gray-700 dark:text-gray-300 font-medium',
|
||||
input:
|
||||
'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
help: 'text-gray-500',
|
||||
spacing: {
|
||||
horizontal: {
|
||||
sm: 'px-2',
|
||||
md: 'px-4',
|
||||
lg: 'px-5'
|
||||
},
|
||||
vertical: {
|
||||
sm: 'py-1.5',
|
||||
md: 'py-2',
|
||||
lg: 'py-3'
|
||||
}
|
||||
},
|
||||
fontSize: {
|
||||
sm: 'text-sm',
|
||||
md: 'text-base',
|
||||
lg: 'text-lg'
|
||||
},
|
||||
borderRadius: {
|
||||
none: 'rounded-none',
|
||||
small: 'rounded-lg',
|
||||
full: 'rounded-[20px]'
|
||||
}
|
||||
},
|
||||
ScaleInput: {
|
||||
button: 'flex-1 appearance-none border-gray-300 dark:border-gray-600 w-full bg-gray-50 text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 text-center',
|
||||
unselectedButton: 'bg-white border'
|
||||
},
|
||||
SliderInput: {
|
||||
stepLabel: 'text-gray-700 dark:text-gray-300 text-center text-xs'
|
||||
},
|
||||
Button: {
|
||||
body: 'transition ease-in duration-200 text-center font-semibold focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-90'
|
||||
},
|
||||
CodeInput: {
|
||||
input: 'overflow-hidden'
|
||||
},
|
||||
RichTextAreaInput: {
|
||||
input:
|
||||
'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-1 focus:ring-opacity-100 focus:border-transparent focus:ring-2'
|
||||
},
|
||||
SelectInput: {
|
||||
input:
|
||||
'relative w-full flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||
dropdown: 'border border-gray-300 dark:border-gray-600',
|
||||
option: 'rounded',
|
||||
minHeight: {
|
||||
sm: 'min-h-[20px]',
|
||||
md: 'min-h-[24px]',
|
||||
lg: 'min-h-[28px]'
|
||||
}
|
||||
},
|
||||
FlatSelectInput: {
|
||||
option: 'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900 flex items-center space-x-2 border-t first:border-t-0 px-2',
|
||||
unselectedIcon: 'text-gray-300 dark:text-gray-600',
|
||||
icon: {
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-5 h-5',
|
||||
lg: 'w-6 h-6 mx-1'
|
||||
}
|
||||
},
|
||||
DateInput: {
|
||||
input: 'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100'
|
||||
},
|
||||
CheckboxInput:{
|
||||
size: {
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-5 h-5',
|
||||
lg: 'w-5 h-5'
|
||||
},
|
||||
},
|
||||
SwitchInput:{
|
||||
containerSize: {
|
||||
sm: 'h-5 w-10',
|
||||
md: 'h-6 w-12',
|
||||
lg: 'h-6 w-12'
|
||||
},
|
||||
circleSize: {
|
||||
sm: 'h-4 w-4',
|
||||
md: 'h-4 w-4',
|
||||
lg: 'h-4 w-4'
|
||||
},
|
||||
},
|
||||
RatingInput:{
|
||||
size: {
|
||||
sm: 'w-6 h-6',
|
||||
md: 'w-8 h-8',
|
||||
lg: 'w-10 h-10'
|
||||
},
|
||||
},
|
||||
fileInput: {
|
||||
input:
|
||||
'border border-dashed border-gray-300 dark:border-gray-600 p-4 shadow-none',
|
||||
minHeight: {
|
||||
sm: 'min-h-28',
|
||||
md: 'min-h-40',
|
||||
lg: 'min-h-48'
|
||||
},
|
||||
inputHover: {
|
||||
light: 'bg-neutral-50',
|
||||
dark: 'bg-notion-dark-light'
|
||||
},
|
||||
uploadedFile:
|
||||
'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light shadow-sm max-w-[10rem]'
|
||||
},
|
||||
SignatureInput: {
|
||||
minHeight: {
|
||||
sm: 'min-h-28',
|
||||
md: 'min-h-40',
|
||||
lg: 'min-h-48'
|
||||
},
|
||||
}
|
||||
},
|
||||
notion: {
|
||||
default: {
|
||||
wrapper: {
|
||||
sm: 'relative mb-2',
|
||||
md: 'relative mb-3',
|
||||
lg: 'relative mb-3',
|
||||
},
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-1 block mt-4',
|
||||
input:
|
||||
'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 dark:placeholder-gray-500 placeholder-gray-400 focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
||||
help: 'text-gray-500',
|
||||
spacing: {
|
||||
horizontal: {
|
||||
sm: 'px-2',
|
||||
md: 'px-4',
|
||||
lg: 'px-5'
|
||||
},
|
||||
vertical: {
|
||||
sm: 'py-1.5',
|
||||
md: 'py-2',
|
||||
lg: 'py-3'
|
||||
}
|
||||
},
|
||||
fontSize: {
|
||||
sm: 'text-sm',
|
||||
md: 'text-base',
|
||||
lg: 'text-lg'
|
||||
},
|
||||
borderRadius: {
|
||||
none: 'rounded-none',
|
||||
small: 'rounded',
|
||||
full: 'rounded-[20px]'
|
||||
}
|
||||
},
|
||||
ScaleInput: {
|
||||
button: 'border-transparent flex-1 appearance-none shadow-inner-notion w-full bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 text-center',
|
||||
unselectedButton: 'bg-notion-input-background dark:bg-notion-dark-light border'
|
||||
},
|
||||
SliderInput: {
|
||||
stepLabel: 'text-gray-700 dark:text-gray-300 text-center text-xs'
|
||||
},
|
||||
Button: {
|
||||
body: 'transition ease-in duration-200 text-center font-semibold shadow shadow-inner-notion focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-90'
|
||||
},
|
||||
CodeInput: {
|
||||
input: 'shadow-inner-notion border-transparent focus:border-transparent overflow-hidden'
|
||||
},
|
||||
RichTextAreaInput: {
|
||||
input:
|
||||
'flex-1 appearance-none shadow-inner-notion border-transparent focus:border-transparent w-full text-gray-900 bg-notion-input-background dark:bg-notion-dark-light dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:ring-opacity-100 focus:border-transparent focus:ring-0 focus:shadow-focus-notion'
|
||||
},
|
||||
SelectInput: {
|
||||
input:
|
||||
'relative w-full border-transparent flex-1 appearance-none bg-notion-input-background shadow-inner-notion w-full text-gray-900 placeholder-gray-400 dark:bg-notion-dark-light dark:placeholder-gray-500 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
||||
dropdown: 'border border-gray-300 dark:border-gray-600',
|
||||
option: 'rounded',
|
||||
minHeight: {
|
||||
sm: 'min-h-[20px]',
|
||||
md: 'min-h-[24px]',
|
||||
lg: 'min-h-[28px]'
|
||||
}
|
||||
},
|
||||
FlatSelectInput: {
|
||||
option: 'cursor-pointer hover:backdrop-brightness-95 flex items-center space-x-2 border-t border-neutral-300 first:border-t-0 px-2',
|
||||
unselectedIcon: 'text-neutral-300 dark:text-neutral-600',
|
||||
icon: {
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-5 h-5',
|
||||
lg: 'w-6 h-6 mx-1'
|
||||
}
|
||||
},
|
||||
DateInput: {
|
||||
input: 'shadow-inner-notion border-transparent focus:border-transparent flex-1 appearance-none w-full bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion p-[1px]'
|
||||
},
|
||||
CheckboxInput:{
|
||||
size: {
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-5 h-5',
|
||||
lg: 'w-5 h-5'
|
||||
},
|
||||
},
|
||||
SwitchInput:{
|
||||
containerSize: {
|
||||
sm: 'h-5 w-10',
|
||||
md: 'h-6 w-12',
|
||||
lg: 'h-6 w-12'
|
||||
},
|
||||
circleSize: {
|
||||
sm: 'h-4 w-4',
|
||||
md: 'h-4 w-4',
|
||||
lg: 'h-4 w-4'
|
||||
},
|
||||
},
|
||||
RatingInput:{
|
||||
size: {
|
||||
sm: 'w-6 h-6',
|
||||
md: 'w-8 h-8',
|
||||
lg: 'w-10 h-10'
|
||||
},
|
||||
},
|
||||
fileInput: {
|
||||
input:
|
||||
'p-4 rounded bg-notion-input-background dark:bg-notion-dark',
|
||||
minHeight: {
|
||||
sm: 'min-h-28',
|
||||
md: 'min-h-40',
|
||||
lg: 'min-h-48'
|
||||
},
|
||||
inputHover: {
|
||||
light: 'bg-neutral-50',
|
||||
dark: 'bg-notion-dark-light'
|
||||
},
|
||||
uploadedFile:
|
||||
'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light rounded shadow-sm max-w-[10rem]'
|
||||
},
|
||||
SignatureInput: {
|
||||
minHeight: {
|
||||
sm: 'min-h-28',
|
||||
md: 'min-h-40',
|
||||
lg: 'min-h-48'
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { themes } from "~/lib/forms/form-themes.js"
|
||||
import { themes } from "~/lib/forms/themes/form-themes.js"
|
||||
import { default as _has } from "lodash/has"
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"prismjs": "^1.24.1",
|
||||
"qrcode": "^1.5.1",
|
||||
"query-builder-vue-3": "^1.0.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tinymotion": "^0.2.0",
|
||||
"v-calendar": "^3.1.2",
|
||||
"vue": "^3.2.13",
|
||||
|
|
@ -13772,11 +13773,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.2.2.tgz",
|
||||
"integrity": "sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.3.0.tgz",
|
||||
"integrity": "sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.0"
|
||||
"@babel/runtime": "^7.24.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
"prismjs": "^1.24.1",
|
||||
"qrcode": "^1.5.1",
|
||||
"query-builder-vue-3": "^1.0.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tinymotion": "^0.2.0",
|
||||
"v-calendar": "^3.1.2",
|
||||
"vue": "^3.2.13",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ module.exports = {
|
|||
"./plugins/**/*.{js,ts}",
|
||||
"./app.vue",
|
||||
"./error.vue",
|
||||
"./lib/forms/form-themes.js",
|
||||
"./lib/forms/themes/form-themes.js",
|
||||
"./lib/forms/themes/ThemeBuilder.js",
|
||||
],
|
||||
safelist: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ class FormFactory extends Factory
|
|||
'description' => $this->faker->randomHtml(1),
|
||||
'visibility' => 'public',
|
||||
'theme' => $this->faker->randomElement(Form::THEMES),
|
||||
'size' => $this->faker->randomElement(Form::SIZES),
|
||||
'border_radius' => $this->faker->randomElement(Form::BORDER_RADIUS),
|
||||
'width' => $this->faker->randomElement(Form::WIDTHS),
|
||||
'dark_mode' => $this->faker->randomElement(Form::DARK_MODE_VALUES),
|
||||
'color' => '#3B82F6',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('forms', function (Blueprint $table) {
|
||||
$table->string('size')->default('md');
|
||||
$table->string('border_radius')->default('small');
|
||||
});
|
||||
|
||||
// Then for each form with "Simple" theme on, disable border radius
|
||||
\App\Models\Forms\Form::whereTheme('simple')->update(['border_radius' => 'none']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('forms', function (Blueprint $table) {
|
||||
$table->dropColumn(['size', 'border_radius']);
|
||||
});
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue