@xy-planning-network/trees 0.7.5-dev → 0.7.5-rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/trees.es.js +4058 -3743
- package/dist/trees.umd.js +10 -10
- package/package.json +2 -2
- package/src/lib-components/forms/BaseInput.vue +80 -42
- package/src/lib-components/forms/Checkbox.vue +36 -26
- package/src/lib-components/forms/DateRangePicker.vue +50 -30
- package/src/lib-components/forms/FieldsetLegend.vue +23 -3
- package/src/lib-components/forms/InputHelp.vue +1 -1
- package/src/lib-components/forms/InputLabel.vue +24 -4
- package/src/lib-components/forms/MultiCheckboxes.vue +95 -44
- package/src/lib-components/forms/Radio.vue +59 -37
- package/src/lib-components/forms/RadioCards.vue +45 -57
- package/src/lib-components/forms/Select.vue +37 -40
- package/src/lib-components/forms/TextArea.vue +36 -28
- package/src/lib-components/forms/Toggle.vue +9 -6
- package/src/lib-components/forms/YesOrNoRadio.vue +49 -35
- package/src/lib-components/lists/DynamicTable.vue +43 -20
- package/types/composables/forms.d.ts +110 -2
- package/types/lib-components/forms/BaseInput.vue.d.ts +60 -34
- package/types/lib-components/forms/Checkbox.vue.d.ts +50 -29
- package/types/lib-components/forms/DateRangePicker.vue.d.ts +71 -39
- package/types/lib-components/forms/FieldsetLegend.vue.d.ts +28 -27
- package/types/lib-components/forms/InputHelp.vue.d.ts +19 -27
- package/types/lib-components/forms/InputLabel.vue.d.ts +28 -27
- package/types/lib-components/forms/MultiCheckboxes.vue.d.ts +75 -47
- package/types/lib-components/forms/Radio.vue.d.ts +62 -47
- package/types/lib-components/forms/RadioCards.vue.d.ts +68 -71
- package/types/lib-components/forms/Select.vue.d.ts +55 -39
- package/types/lib-components/forms/TextArea.vue.d.ts +50 -32
- package/types/lib-components/forms/Toggle.vue.d.ts +27 -32
- package/types/lib-components/forms/YesOrNoRadio.vue.d.ts +50 -32
- package/types/lib-components/indicators/XYSpinner.vue.d.ts +1 -1
- package/types/lib-components/layout/DateFilter.vue.d.ts +28 -20
- package/types/lib-components/layout/SidebarLayout.vue.d.ts +38 -32
- package/types/lib-components/layout/StackedLayout.vue.d.ts +45 -33
- package/types/lib-components/lists/Cards.vue.d.ts +14 -17
- package/types/lib-components/lists/DataTable.vue.d.ts +31 -31
- package/types/lib-components/lists/DetailList.vue.d.ts +38 -34
- package/types/lib-components/lists/DownloadCell.vue.d.ts +18 -15
- package/types/lib-components/lists/DynamicTable.vue.d.ts +32 -32
- package/types/lib-components/lists/TableActionButtons.vue.d.ts +11 -23
- package/types/lib-components/navigation/ActionsDropdown.vue.d.ts +11 -23
- package/types/lib-components/navigation/Paginator.vue.d.ts +12 -15
- package/types/lib-components/navigation/Steps.vue.d.ts +54 -39
- package/types/lib-components/navigation/Tabs.vue.d.ts +34 -34
- package/types/lib-components/overlays/ContentModal.vue.d.ts +22 -28
- package/types/lib-components/overlays/Modal.vue.d.ts +47 -42
- package/types/lib-components/overlays/Popover/Popover.vue.d.ts +23 -31
- package/types/lib-components/overlays/Popover/PopoverContent.vue.d.ts +1 -1
- package/types/lib-components/overlays/Slideover.vue.d.ts +30 -22
- package/types/lib-components/overlays/Tooltip.vue.d.ts +20 -28
|
@@ -1,51 +1,70 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from "vue"
|
|
2
3
|
import FieldsetLegend from "./FieldsetLegend.vue"
|
|
3
4
|
import InputHelp from "./InputHelp.vue"
|
|
4
5
|
import InputLabel from "./InputLabel.vue"
|
|
5
|
-
import { useInputField } from "@/composables/forms"
|
|
6
|
+
import { useInputField, defaultInputProps } from "@/composables/forms"
|
|
7
|
+
import type { OptionsInput, ColumnedInput } from "@/composables/forms"
|
|
6
8
|
|
|
7
9
|
defineOptions({
|
|
8
10
|
inheritAttrs: false,
|
|
9
11
|
})
|
|
10
12
|
|
|
11
|
-
withDefaults(
|
|
12
|
-
defineProps<
|
|
13
|
-
|
|
14
|
-
disabled?: boolean
|
|
15
|
-
help?: string
|
|
16
|
-
label: string
|
|
17
|
-
value: string | number
|
|
18
|
-
}[]
|
|
19
|
-
help?: string
|
|
20
|
-
label?: string
|
|
21
|
-
modelValue?: string | number
|
|
22
|
-
columns?: 2 | 3
|
|
23
|
-
}>(),
|
|
24
|
-
{
|
|
25
|
-
help: "",
|
|
26
|
-
label: "",
|
|
27
|
-
modelValue: undefined,
|
|
28
|
-
columns: undefined,
|
|
29
|
-
}
|
|
13
|
+
const props = withDefaults(
|
|
14
|
+
defineProps<OptionsInput & ColumnedInput>(),
|
|
15
|
+
defaultInputProps
|
|
30
16
|
)
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
|
|
18
|
+
defineEmits(["update:modelValue", "update:error"])
|
|
19
|
+
|
|
20
|
+
const radios = ref<HTMLInputElement[]>([])
|
|
21
|
+
// there are multiple radio buttons that could be the target
|
|
22
|
+
// for validation set to the first input
|
|
23
|
+
const targetInput = computed(() => {
|
|
24
|
+
if (radios.value.length === 0) {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return radios.value[0]
|
|
29
|
+
})
|
|
30
|
+
const {
|
|
31
|
+
errorState,
|
|
32
|
+
modelState,
|
|
33
|
+
inputID,
|
|
34
|
+
isDisabled,
|
|
35
|
+
isRequired,
|
|
36
|
+
onInvalid,
|
|
37
|
+
validate,
|
|
38
|
+
} = useInputField({ props, targetInput })
|
|
39
|
+
|
|
40
|
+
const onChange = (e: Event, val: string | number) => {
|
|
41
|
+
modelState.value = val
|
|
42
|
+
validate(e)
|
|
43
|
+
}
|
|
33
44
|
</script>
|
|
34
45
|
|
|
35
46
|
<template>
|
|
36
47
|
<fieldset
|
|
37
|
-
class="space-y-
|
|
48
|
+
class="space-y-4"
|
|
38
49
|
:aria-labelledby="label ? `${inputID}-legend` : undefined"
|
|
39
50
|
:aria-describedby="help ? `${inputID}-help` : undefined"
|
|
40
51
|
>
|
|
41
52
|
<div v-if="label">
|
|
42
|
-
<FieldsetLegend
|
|
53
|
+
<FieldsetLegend
|
|
54
|
+
:id="`${inputID}-legend`"
|
|
55
|
+
:label="label"
|
|
56
|
+
:required="isRequired"
|
|
57
|
+
/>
|
|
43
58
|
<InputHelp v-if="help" :id="`${inputID}-help`" tag="p" :text="help" />
|
|
44
59
|
</div>
|
|
45
60
|
|
|
61
|
+
<div v-if="errorState" class="mt-0.5">
|
|
62
|
+
<p class="text-sm text-red-700">{{ errorState }}</p>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
46
65
|
<div class="flex">
|
|
47
66
|
<div
|
|
48
|
-
class="grid gap-y-
|
|
67
|
+
class="grid gap-y-6"
|
|
49
68
|
:class="{
|
|
50
69
|
'sm:grid sm:gap-x-5 sm:space-y-0': columns !== undefined,
|
|
51
70
|
'sm:grid-cols-2': columns === 2,
|
|
@@ -57,30 +76,30 @@ const { inputID, isDisabled } = useInputField()
|
|
|
57
76
|
:key="option.value"
|
|
58
77
|
class="flex items-start"
|
|
59
78
|
>
|
|
60
|
-
<div class="flex items-center h-
|
|
79
|
+
<div class="flex items-center h-5">
|
|
61
80
|
<input
|
|
62
81
|
:id="`${inputID}-${index}`"
|
|
82
|
+
ref="radios"
|
|
63
83
|
:aria-describedby="
|
|
64
84
|
option.help ? `${inputID}-${index}-help` : undefined
|
|
65
85
|
"
|
|
66
86
|
:aria-labelledby="`${inputID}-${index}-label`"
|
|
67
|
-
:checked="
|
|
87
|
+
:checked="modelState === option.value"
|
|
68
88
|
:class="[
|
|
69
|
-
'h-4 w-4',
|
|
70
|
-
'border-gray-300 text-xy-blue focus:ring-xy-blue-500',
|
|
89
|
+
'h-4 w-4 cursor-pointer text-xy-blue',
|
|
71
90
|
'disabled:bg-gray-100 disabled:border-gray-200 disabled:cursor-not-allowed disabled:opacity-100',
|
|
72
91
|
'checked:disabled:bg-xy-blue checked:disabled:border-xy-blue checked:disabled:opacity-50',
|
|
92
|
+
errorState
|
|
93
|
+
? 'border-red-700 focus:ring-red-700'
|
|
94
|
+
: 'border-gray-300 focus:ring-xy-blue-500',
|
|
73
95
|
]"
|
|
74
96
|
:disabled="option.disabled"
|
|
75
97
|
:name="inputID"
|
|
76
98
|
type="radio"
|
|
77
99
|
:value="option.value"
|
|
78
|
-
v-bind="
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
},
|
|
82
|
-
...$attrs,
|
|
83
|
-
}"
|
|
100
|
+
v-bind="$attrs"
|
|
101
|
+
@change="onChange($event, option.value)"
|
|
102
|
+
@invalid="onInvalid"
|
|
84
103
|
/>
|
|
85
104
|
</div>
|
|
86
105
|
<div class="ml-3">
|
|
@@ -88,9 +107,12 @@ const { inputID, isDisabled } = useInputField()
|
|
|
88
107
|
:id="`${inputID}-${index}-label`"
|
|
89
108
|
:for="`${inputID}-${index}`"
|
|
90
109
|
:label="option.label"
|
|
91
|
-
:class="
|
|
110
|
+
:class="
|
|
111
|
+
isDisabled || option.disabled
|
|
112
|
+
? 'cursor-not-allowed'
|
|
113
|
+
: 'cursor-pointer'
|
|
114
|
+
"
|
|
92
115
|
/>
|
|
93
|
-
|
|
94
116
|
<InputHelp :id="`${inputID}-${index}-help`" :text="option.help" />
|
|
95
117
|
</div>
|
|
96
118
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
1
|
+
<script setup lang="ts" generic="T extends InputOption">
|
|
2
2
|
import {
|
|
3
3
|
RadioGroup,
|
|
4
4
|
RadioGroupDescription,
|
|
@@ -6,11 +6,16 @@ import {
|
|
|
6
6
|
RadioGroupOption,
|
|
7
7
|
} from "@headlessui/vue"
|
|
8
8
|
import { CheckCircleIcon } from "@heroicons/vue/solid"
|
|
9
|
-
import { computed, ref } from "vue"
|
|
10
9
|
import InputLabel from "./InputLabel.vue"
|
|
11
10
|
import InputHelp from "./InputHelp.vue"
|
|
12
11
|
import FieldsetLegend from "./FieldsetLegend.vue"
|
|
13
|
-
import { useInputField } from "@/composables/forms"
|
|
12
|
+
import { defaultInputProps, useInputField } from "@/composables/forms"
|
|
13
|
+
import type {
|
|
14
|
+
ColumnedInput,
|
|
15
|
+
InputOption,
|
|
16
|
+
OptionsInput,
|
|
17
|
+
} from "@/composables/forms"
|
|
18
|
+
import { computed, ref } from "vue"
|
|
14
19
|
|
|
15
20
|
defineOptions({
|
|
16
21
|
inheritAttrs: false,
|
|
@@ -20,83 +25,63 @@ defineOptions({
|
|
|
20
25
|
* NOTE (spk) headless UI introduced a "name" prop that includes a hidden field
|
|
21
26
|
* to use the modelValue inside of forms. It does not however resolve the issue of
|
|
22
27
|
* supporting HTML5 form validation, so we'll add our own hidden radio buttons to support both.
|
|
23
|
-
*
|
|
24
|
-
* The headless technique does include supporting complex modelValues such as objects, which we may
|
|
25
|
-
* need in the future. We can revist required validation at that time using a singular hidden checkbox.
|
|
26
28
|
*/
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
type RadioCard = {
|
|
31
|
-
disabled?: boolean
|
|
32
|
-
help?: string
|
|
33
|
-
label: string
|
|
34
|
-
sublabel?: string
|
|
35
|
-
value: ModelValue
|
|
30
|
+
interface RadioCards extends OptionsInput {
|
|
31
|
+
options: T[]
|
|
36
32
|
}
|
|
37
33
|
|
|
38
34
|
const props = withDefaults(
|
|
39
|
-
defineProps<
|
|
40
|
-
|
|
41
|
-
help?: string
|
|
42
|
-
label?: string
|
|
43
|
-
modelValue?: ModelValue
|
|
44
|
-
options: RadioCard[]
|
|
45
|
-
}>(),
|
|
46
|
-
{
|
|
47
|
-
columns: undefined,
|
|
48
|
-
help: "",
|
|
49
|
-
label: "",
|
|
50
|
-
modelValue: undefined,
|
|
51
|
-
}
|
|
35
|
+
defineProps<RadioCards & ColumnedInput>(),
|
|
36
|
+
defaultInputProps
|
|
52
37
|
)
|
|
53
38
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// allows v-model to be undefined by the consumer but still supports
|
|
62
|
-
// the display requirements of the component.
|
|
63
|
-
// this is usful when the component is used inside a form element and
|
|
64
|
-
// tracking v-model isn't required.
|
|
65
|
-
const internalState = ref()
|
|
66
|
-
const invalid = ref<boolean>()
|
|
67
|
-
const checkedState = computed(() => {
|
|
68
|
-
if (props.modelValue === undefined) {
|
|
69
|
-
return internalState.value
|
|
39
|
+
defineEmits(["update:modelValue", "update:error"])
|
|
40
|
+
const hiddenRadios = ref<HTMLInputElement[]>([])
|
|
41
|
+
// there are multiple radio buttons that could be the target
|
|
42
|
+
// for validation set to the first input
|
|
43
|
+
const targetInput = computed(() => {
|
|
44
|
+
if (hiddenRadios.value.length === 0) {
|
|
45
|
+
return null
|
|
70
46
|
}
|
|
71
47
|
|
|
72
|
-
return
|
|
48
|
+
return hiddenRadios.value[0]
|
|
73
49
|
})
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
50
|
+
const {
|
|
51
|
+
inputID,
|
|
52
|
+
isDisabled,
|
|
53
|
+
isRequired,
|
|
54
|
+
nameAttr,
|
|
55
|
+
modelState,
|
|
56
|
+
errorState,
|
|
57
|
+
onInvalid,
|
|
58
|
+
} = useInputField({ props, targetInput })
|
|
59
|
+
|
|
60
|
+
const onUpdate = (val: unknown) => {
|
|
61
|
+
if (val) {
|
|
62
|
+
errorState.value = ""
|
|
63
|
+
}
|
|
79
64
|
}
|
|
80
65
|
</script>
|
|
81
66
|
|
|
82
67
|
<template>
|
|
83
68
|
<RadioGroup
|
|
84
|
-
|
|
69
|
+
v-model="modelState"
|
|
85
70
|
:disabled="isDisabled"
|
|
86
|
-
:aria-invalid="
|
|
87
|
-
:aria-errormessage="
|
|
88
|
-
@update:model-value="
|
|
71
|
+
:aria-invalid="errorState ? 'true' : null"
|
|
72
|
+
:aria-errormessage="errorState ? `error-${inputID}` : null"
|
|
73
|
+
@update:model-value="onUpdate"
|
|
89
74
|
>
|
|
90
75
|
<RadioGroupLabel v-if="label" class="block">
|
|
91
|
-
<FieldsetLegend tag="div" :label="label" />
|
|
76
|
+
<FieldsetLegend tag="div" :label="label" :required="isRequired" />
|
|
92
77
|
</RadioGroupLabel>
|
|
93
78
|
|
|
94
79
|
<RadioGroupDescription v-if="help">
|
|
95
80
|
<InputHelp :text="help" />
|
|
96
81
|
</RadioGroupDescription>
|
|
97
82
|
|
|
98
|
-
<div v-if="
|
|
99
|
-
|
|
83
|
+
<div v-if="errorState" :id="`error-${inputID}`">
|
|
84
|
+
<p class="text-sm text-red-700">{{ errorState }}</p>
|
|
100
85
|
</div>
|
|
101
86
|
|
|
102
87
|
<div
|
|
@@ -128,6 +113,7 @@ const onChange = (val: ModelValue) => {
|
|
|
128
113
|
disabled
|
|
129
114
|
? 'cursor-not-allowed bg-gray-50 border-gray-200 opacity-90'
|
|
130
115
|
: 'cursor-pointer bg-white border-gray-300',
|
|
116
|
+
errorState && !disabled ? 'border-red-700' : '',
|
|
131
117
|
checked ? 'border-transparent' : '',
|
|
132
118
|
active ? 'border-xy-blue ring-2 ring-xy-blue-500' : '',
|
|
133
119
|
]"
|
|
@@ -179,7 +165,9 @@ const onChange = (val: ModelValue) => {
|
|
|
179
165
|
aria-hidden="true"
|
|
180
166
|
/>
|
|
181
167
|
|
|
168
|
+
<!--TODO: (spk) ideally this would trigger a change event -->
|
|
182
169
|
<input
|
|
170
|
+
ref="hiddenRadios"
|
|
183
171
|
class="sr-only top-1 left-1"
|
|
184
172
|
aria-hidden="true"
|
|
185
173
|
:checked="checked"
|
|
@@ -188,7 +176,7 @@ const onChange = (val: ModelValue) => {
|
|
|
188
176
|
tabindex="-1"
|
|
189
177
|
type="radio"
|
|
190
178
|
:value="option.value"
|
|
191
|
-
@invalid="
|
|
179
|
+
@invalid="onInvalid"
|
|
192
180
|
/>
|
|
193
181
|
</div>
|
|
194
182
|
</RadioGroupOption>
|
|
@@ -1,71 +1,68 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import InputLabel from "./InputLabel.vue"
|
|
3
3
|
import InputHelp from "./InputHelp.vue"
|
|
4
|
-
import { useInputField } from "@/composables/forms"
|
|
4
|
+
import { defaultInputProps, useInputField } from "@/composables/forms"
|
|
5
|
+
import type { OptionsInput } from "@/composables/forms"
|
|
6
|
+
import { ref } from "vue"
|
|
5
7
|
|
|
6
8
|
defineOptions({
|
|
7
9
|
inheritAttrs: false,
|
|
8
10
|
})
|
|
9
11
|
|
|
10
|
-
withDefaults(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
label: "",
|
|
20
|
-
help: "",
|
|
21
|
-
placeholder: "Select an option",
|
|
22
|
-
}
|
|
23
|
-
)
|
|
12
|
+
const props = withDefaults(defineProps<OptionsInput>(), {
|
|
13
|
+
...defaultInputProps,
|
|
14
|
+
placeholder: "Select an option",
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
defineEmits(["update:modelValue", "update:error"])
|
|
18
|
+
const targetInput = ref<HTMLInputElement | null>(null)
|
|
19
|
+
const { inputID, isRequired, validate, modelState, errorState, onInvalid } =
|
|
20
|
+
useInputField({ props, targetInput })
|
|
24
21
|
|
|
25
|
-
const
|
|
26
|
-
|
|
22
|
+
const onChange = (e: Event) => {
|
|
23
|
+
modelState.value = (e.target as HTMLInputElement).value
|
|
24
|
+
validate(e)
|
|
25
|
+
}
|
|
27
26
|
</script>
|
|
28
27
|
|
|
29
28
|
<template>
|
|
30
29
|
<div>
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
<InputLabel
|
|
31
|
+
:id="`${inputID}-label`"
|
|
32
|
+
class="mb-2"
|
|
33
|
+
:for="inputID"
|
|
34
|
+
:label="label"
|
|
35
|
+
:required="isRequired"
|
|
36
|
+
/>
|
|
34
37
|
<select
|
|
35
38
|
:id="inputID"
|
|
39
|
+
ref="targetInput"
|
|
36
40
|
:aria-labelledby="label ? `${inputID}-label` : undefined"
|
|
37
41
|
:aria-describedby="help ? `${inputID}-help` : undefined"
|
|
38
42
|
:class="[
|
|
39
43
|
'block w-full rounded-md border-0 py-2 shadow-sm ring-1 ring-inset focus:ring-2 sm:text-sm sm:leading-6 pl-3 pr-10',
|
|
40
|
-
'disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-
|
|
41
|
-
|
|
42
|
-
? 'text-
|
|
43
|
-
: 'text-
|
|
44
|
+
'disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-700 disabled:ring-gray-200 disabled:opacity-100',
|
|
45
|
+
errorState
|
|
46
|
+
? 'text-red-900 ring-red-700 placeholder:text-red-300 focus:ring-red-700'
|
|
47
|
+
: 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-xy-blue-500',
|
|
44
48
|
]"
|
|
45
|
-
:value="
|
|
46
|
-
v-bind="
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
emit('update:modelValue', ($event.target as HTMLInputElement).value)
|
|
50
|
-
},
|
|
51
|
-
}"
|
|
49
|
+
:value="modelState"
|
|
50
|
+
v-bind="$attrs"
|
|
51
|
+
@change="onChange"
|
|
52
|
+
@invalid="onInvalid"
|
|
52
53
|
>
|
|
53
|
-
<option
|
|
54
|
-
v-if="placeholder"
|
|
55
|
-
value=""
|
|
56
|
-
disabled
|
|
57
|
-
selected
|
|
58
|
-
v-text="placeholder"
|
|
59
|
-
/>
|
|
54
|
+
<option value="" disabled selected v-text="placeholder" />
|
|
60
55
|
<option
|
|
61
56
|
v-for="option in options"
|
|
62
57
|
:key="option.value"
|
|
63
58
|
:value="option.value"
|
|
59
|
+
:disabled="option.disabled"
|
|
64
60
|
v-text="option.label"
|
|
65
61
|
/>
|
|
66
62
|
</select>
|
|
67
|
-
<
|
|
68
|
-
|
|
63
|
+
<InputHelp :id="`${inputID}-help`" class="mt-1" :text="help" />
|
|
64
|
+
<div v-if="errorState" class="mt-0.5">
|
|
65
|
+
<p class="text-sm text-red-700">{{ errorState }}</p>
|
|
69
66
|
</div>
|
|
70
67
|
</div>
|
|
71
68
|
</template>
|
|
@@ -1,53 +1,61 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import InputLabel from "./InputLabel.vue"
|
|
3
3
|
import InputHelp from "./InputHelp.vue"
|
|
4
|
-
import { useInputField } from "@/composables/forms"
|
|
4
|
+
import { useInputField, defaultInputProps } from "@/composables/forms"
|
|
5
|
+
import type { TextareaInput } from "@/composables/forms"
|
|
6
|
+
import { ref } from "vue"
|
|
5
7
|
|
|
6
8
|
defineOptions({
|
|
7
9
|
inheritAttrs: false,
|
|
8
10
|
})
|
|
9
11
|
|
|
10
|
-
withDefaults(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
)
|
|
12
|
+
const props = withDefaults(defineProps<TextareaInput>(), defaultInputProps)
|
|
13
|
+
defineEmits(["update:modelValue", "update:error"])
|
|
14
|
+
const targetInput = ref<HTMLInputElement | null>(null)
|
|
15
|
+
const {
|
|
16
|
+
inputID,
|
|
17
|
+
isRequired,
|
|
18
|
+
modelState,
|
|
19
|
+
errorState,
|
|
20
|
+
onInvalid,
|
|
21
|
+
inputValidation,
|
|
22
|
+
} = useInputField({ props, targetInput })
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
-
|
|
24
|
+
const onInput = (e: Event) => {
|
|
25
|
+
modelState.value = (e.target as HTMLInputElement).value
|
|
26
|
+
inputValidation(e)
|
|
27
|
+
}
|
|
25
28
|
</script>
|
|
26
29
|
|
|
27
30
|
<template>
|
|
28
31
|
<div>
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
<InputLabel
|
|
33
|
+
:id="`${inputID}-label`"
|
|
34
|
+
class="mb-2"
|
|
35
|
+
:for="inputID"
|
|
36
|
+
:label="label"
|
|
37
|
+
:required="isRequired"
|
|
38
|
+
/>
|
|
32
39
|
<textarea
|
|
33
40
|
:id="inputID"
|
|
41
|
+
ref="targetInput"
|
|
34
42
|
:aria-labelledby="label ? `${inputID}-label` : undefined"
|
|
35
43
|
:aria-describedby="help ? `${inputID}-help` : undefined"
|
|
36
44
|
:class="[
|
|
37
45
|
'block w-full rounded-md border-0 py-2 shadow-sm ring-1 ring-inset focus:ring-2 sm:text-sm sm:leading-6',
|
|
38
|
-
'disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-
|
|
39
|
-
|
|
40
|
-
? 'text-
|
|
41
|
-
: 'text-
|
|
46
|
+
'disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-700 disabled:ring-gray-200',
|
|
47
|
+
errorState
|
|
48
|
+
? 'text-red-900 ring-red-700 placeholder:text-red-300 focus:ring-red-700'
|
|
49
|
+
: 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-xy-blue-500',
|
|
42
50
|
]"
|
|
43
|
-
:value="
|
|
51
|
+
:value="modelState || undefined"
|
|
44
52
|
v-bind="$attrs"
|
|
45
|
-
@input="
|
|
46
|
-
|
|
47
|
-
"
|
|
53
|
+
@input="onInput"
|
|
54
|
+
@invalid="onInvalid"
|
|
48
55
|
/>
|
|
49
|
-
<
|
|
50
|
-
|
|
56
|
+
<InputHelp :id="`${inputID}-help`" class="mb-1" :text="help"></InputHelp>
|
|
57
|
+
<div v-if="errorState" class="mt-0.5">
|
|
58
|
+
<p class="text-sm text-red-700">{{ errorState }}</p>
|
|
51
59
|
</div>
|
|
52
60
|
</div>
|
|
53
61
|
</template>
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
} from "@headlessui/vue"
|
|
8
8
|
import InputLabel from "@/lib-components/forms/InputLabel.vue"
|
|
9
9
|
import InputHelp from "@/lib-components/forms/InputHelp.vue"
|
|
10
|
-
import {
|
|
10
|
+
import { hasAttribute } from "@/composables/forms"
|
|
11
|
+
import { computed, useAttrs } from "vue"
|
|
11
12
|
|
|
12
13
|
defineOptions({
|
|
13
14
|
inheritAttrs: false,
|
|
@@ -15,19 +16,21 @@ defineOptions({
|
|
|
15
16
|
|
|
16
17
|
withDefaults(
|
|
17
18
|
defineProps<{
|
|
18
|
-
modelValue?: boolean
|
|
19
|
+
modelValue?: boolean
|
|
19
20
|
label?: string
|
|
20
21
|
help?: string
|
|
21
22
|
}>(),
|
|
22
23
|
{
|
|
23
|
-
modelValue: undefined,
|
|
24
24
|
label: "",
|
|
25
25
|
help: "",
|
|
26
26
|
}
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
const emits = defineEmits(["update:modelValue"])
|
|
30
|
-
const
|
|
30
|
+
const attrs = useAttrs()
|
|
31
|
+
const isDisabled = computed(() => {
|
|
32
|
+
return hasAttribute(attrs, "disabled")
|
|
33
|
+
})
|
|
31
34
|
</script>
|
|
32
35
|
<template>
|
|
33
36
|
<SwitchGroup as="div" class="flex items-start">
|
|
@@ -51,11 +54,11 @@ const { isDisabled } = useInputField()
|
|
|
51
54
|
/>
|
|
52
55
|
</Switch>
|
|
53
56
|
|
|
54
|
-
<div class="ml-3">
|
|
57
|
+
<div class="ml-3 mt-0.5">
|
|
55
58
|
<SwitchLabel v-if="label">
|
|
56
59
|
<InputLabel
|
|
57
60
|
:label="label"
|
|
58
|
-
:class="isDisabled
|
|
61
|
+
:class="isDisabled ? 'cursor-not-allowed' : 'cursor-pointer'"
|
|
59
62
|
/>
|
|
60
63
|
</SwitchLabel>
|
|
61
64
|
|