@xy-planning-network/trees 0.7.4 → 0.7.5-rc1

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.
Files changed (58) hide show
  1. package/dist/trees.es.js +3939 -3561
  2. package/dist/trees.umd.js +10 -10
  3. package/package.json +2 -2
  4. package/src/index.css +0 -16
  5. package/src/lib-components/forms/BaseInput.vue +90 -75
  6. package/src/lib-components/forms/Checkbox.vue +50 -34
  7. package/src/lib-components/forms/DateRangePicker.vue +56 -32
  8. package/src/lib-components/forms/FieldsetLegend.vue +28 -8
  9. package/src/lib-components/forms/InputHelp.vue +2 -4
  10. package/src/lib-components/forms/InputLabel.vue +27 -12
  11. package/src/lib-components/forms/MultiCheckboxes.vue +117 -74
  12. package/src/lib-components/forms/Radio.vue +79 -66
  13. package/src/lib-components/forms/RadioCards.vue +72 -70
  14. package/src/lib-components/forms/Select.vue +59 -56
  15. package/src/lib-components/forms/TextArea.vue +54 -47
  16. package/src/lib-components/forms/Toggle.vue +61 -18
  17. package/src/lib-components/forms/YesOrNoRadio.vue +75 -67
  18. package/src/lib-components/lists/DynamicTable.vue +43 -20
  19. package/types/composables/forms.d.ts +118 -0
  20. package/types/helpers/Debounce.d.ts +1 -1
  21. package/types/lib-components/forms/BaseInput.vue.d.ts +58 -34
  22. package/types/lib-components/forms/Checkbox.vue.d.ts +50 -29
  23. package/types/lib-components/forms/DateRangePicker.vue.d.ts +71 -39
  24. package/types/lib-components/forms/FieldsetLegend.vue.d.ts +30 -31
  25. package/types/lib-components/forms/InputHelp.vue.d.ts +19 -27
  26. package/types/lib-components/forms/InputLabel.vue.d.ts +28 -32
  27. package/types/lib-components/forms/MultiCheckboxes.vue.d.ts +77 -56
  28. package/types/lib-components/forms/Radio.vue.d.ts +64 -56
  29. package/types/lib-components/forms/RadioCards.vue.d.ts +68 -71
  30. package/types/lib-components/forms/Select.vue.d.ts +55 -44
  31. package/types/lib-components/forms/TextArea.vue.d.ts +50 -32
  32. package/types/lib-components/forms/Toggle.vue.d.ts +29 -24
  33. package/types/lib-components/forms/YesOrNoRadio.vue.d.ts +51 -38
  34. package/types/lib-components/indicators/XYSpinner.vue.d.ts +1 -1
  35. package/types/lib-components/layout/DateFilter.vue.d.ts +28 -20
  36. package/types/lib-components/layout/SidebarLayout.vue.d.ts +38 -32
  37. package/types/lib-components/layout/StackedLayout.vue.d.ts +45 -33
  38. package/types/lib-components/lists/Cards.vue.d.ts +14 -17
  39. package/types/lib-components/lists/DataTable.vue.d.ts +31 -31
  40. package/types/lib-components/lists/DetailList.vue.d.ts +38 -34
  41. package/types/lib-components/lists/DownloadCell.vue.d.ts +18 -15
  42. package/types/lib-components/lists/DynamicTable.vue.d.ts +32 -32
  43. package/types/lib-components/lists/StaticTable.vue.d.ts +21 -0
  44. package/types/lib-components/lists/StaticTableActionSlot.vue.d.ts +27 -0
  45. package/types/lib-components/lists/Table.vue.d.ts +39 -0
  46. package/types/lib-components/lists/TableActionButtons.vue.d.ts +11 -23
  47. package/types/lib-components/navigation/ActionsDropdown.vue.d.ts +11 -23
  48. package/types/lib-components/navigation/ActionsDropdownCallback.vue.d.ts +18 -0
  49. package/types/lib-components/navigation/ActionsDropdownEmit.vue.d.ts +22 -0
  50. package/types/lib-components/navigation/Paginator.vue.d.ts +12 -15
  51. package/types/lib-components/navigation/Steps.vue.d.ts +54 -39
  52. package/types/lib-components/navigation/Tabs.vue.d.ts +34 -34
  53. package/types/lib-components/overlays/ContentModal.vue.d.ts +22 -28
  54. package/types/lib-components/overlays/Modal.vue.d.ts +48 -43
  55. package/types/lib-components/overlays/Popover/Popover.vue.d.ts +23 -31
  56. package/types/lib-components/overlays/Popover/PopoverContent.vue.d.ts +1 -1
  57. package/types/lib-components/overlays/Slideover.vue.d.ts +30 -22
  58. package/types/lib-components/overlays/Tooltip.vue.d.ts +20 -28
@@ -1,75 +1,122 @@
1
1
  <script setup lang="ts">
2
- import Uniques from "@/helpers/Uniques"
3
- import { computed, useAttrs, useSlots } from "vue"
4
2
  import FieldsetLegend from "./FieldsetLegend.vue"
5
3
  import InputLabel from "./InputLabel.vue"
6
4
  import InputHelp from "./InputHelp.vue"
5
+ import { useInputField, defaultInputProps } from "@/composables/forms"
6
+ import type { MultiChoiceInput, ColumnedInput } from "@/composables/forms"
7
+ import { computed, ref } from "vue"
7
8
 
8
- type CheckboxValue = string | number
9
- type ModelValue = CheckboxValue[]
9
+ defineOptions({
10
+ inheritAttrs: false,
11
+ })
10
12
 
11
13
  const props = withDefaults(
12
- defineProps<{
13
- options: {
14
- disabled?: boolean
15
- help?: string
16
- label: string
17
- value: CheckboxValue
18
- }[]
19
- help?: string
20
- legend?: string
21
- modelValue: ModelValue
22
- columns?: 2 | 3 | 4
23
- }>(),
24
- {
25
- help: "",
26
- legend: "",
27
- columns: undefined,
28
- }
14
+ defineProps<MultiChoiceInput & ColumnedInput>(),
15
+ defaultInputProps
29
16
  )
30
17
 
31
- const emit = defineEmits<{
32
- (e: "update:modelValue", modelValue: ModelValue): void
33
- }>()
34
- const attrs = useAttrs()
35
- const slots = useSlots()
36
- const uuid = (attrs.id as string) || Uniques.CreateIdAttribute()
37
- const hasLegend = computed(() => {
38
- return props.legend !== "" || slots.legend !== undefined
39
- })
40
- const onChange = (checked: boolean, val: CheckboxValue) => {
41
- let updateModelValue = [...props.modelValue]
18
+ defineEmits(["update:modelValue", "update:error"])
19
+ const targetInput = ref<HTMLInputElement | null>(null)
20
+ const { inputID, isDisabled, modelState, errorState, validate } = useInputField(
21
+ { props, targetInput }
22
+ )
23
+
24
+ const onChange = (e: Event, val: string | number) => {
25
+ const checked = (e.target as HTMLInputElement).checked
26
+
27
+ if (!Array.isArray(modelState.value)) {
28
+ modelState.value = []
29
+ }
42
30
 
43
31
  if (checked) {
44
- updateModelValue.push(val)
32
+ modelState.value.push(val)
45
33
  } else {
46
- updateModelValue.splice(updateModelValue.indexOf(val), 1)
34
+ modelState.value.splice(modelState.value.indexOf(val), 1)
35
+ }
36
+
37
+ validate(e)
38
+ }
39
+
40
+ const selectionCount = computed(() => {
41
+ if (Array.isArray(modelState.value)) {
42
+ return modelState.value.length
43
+ }
44
+
45
+ return 0
46
+ })
47
+
48
+ const minCount = computed(() => {
49
+ return props.min || 0
50
+ })
51
+
52
+ const maxCount = computed(() => {
53
+ return (
54
+ props.max ||
55
+ props.options.filter((opt) => {
56
+ return !opt.disabled
57
+ }).length
58
+ )
59
+ })
60
+
61
+ const countError = computed(() => {
62
+ if (selectionCount.value < minCount.value) {
63
+ return `Please select ${minCount.value} of these option${
64
+ minCount.value > 1 ? "s" : ""
65
+ }.`
66
+ }
67
+
68
+ if (selectionCount.value > maxCount.value) {
69
+ return `Please select only ${maxCount.value} of these option${
70
+ maxCount.value > 1 ? "s" : ""
71
+ }.`
47
72
  }
48
73
 
49
- emit("update:modelValue", updateModelValue)
74
+ return ""
75
+ })
76
+
77
+ const setValidationError = () => {
78
+ if (!errorState.value) {
79
+ errorState.value = countError.value
80
+ }
50
81
  }
51
82
  </script>
83
+
52
84
  <template>
53
85
  <fieldset
54
- class="space-y-5"
55
- :aria-labelledby="hasLegend ? `${uuid}-legend` : undefined"
56
- :aria-describedby="help ? `${uuid}-help` : undefined"
86
+ class="relative space-y-4"
87
+ :aria-labelledby="label ? `${inputID}-legend` : undefined"
88
+ :aria-describedby="help ? `${inputID}-help` : undefined"
57
89
  >
58
- <div v-if="hasLegend || help" class="space-y-0.5">
59
- <FieldsetLegend :id="`${uuid}-legend`">
60
- <div v-if="legend">{{ legend }}</div>
61
- <slot v-if="$slots.legend" name="legend"></slot>
62
- </FieldsetLegend>
63
- <InputHelp :id="`${uuid}-help`" tag="p" :text="help" />
90
+ <div v-if="label">
91
+ <FieldsetLegend
92
+ :id="`${inputID}-legend`"
93
+ :label="label"
94
+ :required="minCount > 0"
95
+ />
96
+ <InputHelp v-if="help" :id="`${inputID}-help`" tag="p" :text="help" />
97
+ </div>
98
+
99
+ <div v-if="errorState" class="mt-0.5">
100
+ <p class="text-sm text-red-700">{{ errorState }}</p>
64
101
  </div>
102
+
103
+ <input
104
+ v-if="countError || errorState"
105
+ ref="targetInput"
106
+ required
107
+ class="sr-only top-1 left-1"
108
+ aria-hidden
109
+ type="checkbox"
110
+ @invalid="setValidationError"
111
+ />
112
+
65
113
  <div class="flex">
66
114
  <div
67
- class="grid gap-4"
115
+ class="grid gap-y-6"
68
116
  :class="{
69
- 'sm:grid sm:gap-y-4 sm:gap-x-5 sm:space-y-0': columns !== undefined,
117
+ 'sm:grid sm:gap-x-5 sm:space-y-0': columns !== undefined,
70
118
  'sm:grid-cols-2': columns === 2,
71
119
  'sm:grid-cols-3': columns === 3,
72
- 'sm:grid-cols-4': columns === 4,
73
120
  }"
74
121
  >
75
122
  <div
@@ -79,42 +126,38 @@ const onChange = (checked: boolean, val: CheckboxValue) => {
79
126
  >
80
127
  <div class="flex items-center h-5">
81
128
  <input
82
- :id="`${uuid}-${index}`"
83
- :aria-labelledby="`${uuid}-${index}-label`"
129
+ :id="`${inputID}-${index}`"
130
+ :aria-labelledby="`${inputID}-${index}-label`"
84
131
  :aria-describedby="
85
- option?.help && option.help
86
- ? `${uuid}-${index}-help`
87
- : undefined
132
+ option.help ? `${inputID}-${index}-help` : undefined
88
133
  "
89
- :checked="modelValue.includes(option.value)"
90
- :disabled="option.disabled === true ? true : undefined"
91
- class="focus:ring-xy-blue-500 h-4 w-4 text-xy-blue border-gray-600 rounded disabled:opacity-50 disabled:cursor-not-allowed"
134
+ :checked="modelValue?.includes(option.value)"
135
+ :disabled="option.disabled"
136
+ :class="[
137
+ 'h-4 w-4 rounded cursor-pointer',
138
+ 'disabled:bg-gray-100 disabled:border-gray-200 disabled:cursor-not-allowed disabled:opacity-100',
139
+ 'checked:disabled:bg-xy-blue checked:disabled:border-xy-blue checked:disabled:opacity-50',
140
+ errorState
141
+ ? 'border-red-700 focus:ring-red-700'
142
+ : 'border-gray-300 focus:ring-xy-blue-500',
143
+ ]"
92
144
  type="checkbox"
93
- v-bind="{
94
- onChange: ($event) => {
95
- onChange(($event.target as HTMLInputElement).checked, option.value)
96
- },
97
- ...$attrs,
98
- }"
145
+ v-bind="$attrs"
146
+ @change="onChange($event, option.value)"
99
147
  />
100
148
  </div>
101
149
  <div class="ml-3">
102
150
  <InputLabel
103
- :id="`${uuid}-${index}-label`"
104
- class="mt-auto"
105
- :disabled="
106
- ($attrs.hasOwnProperty('disabled') &&
107
- $attrs.disabled !== false) ||
108
- option.disabled === true
109
- "
110
- :for="`${uuid}-${index}`"
151
+ :id="`${inputID}-${index}-label`"
152
+ :for="`${inputID}-${index}`"
111
153
  :label="option.label"
154
+ :class="
155
+ isDisabled || option.disabled
156
+ ? 'cursor-not-allowed'
157
+ : 'cursor-pointer'
158
+ "
112
159
  />
113
- <InputHelp
114
- :id="`${uuid}-${index}-help`"
115
- class="-mt-1"
116
- :text="option.help"
117
- />
160
+ <InputHelp :id="`${inputID}-${index}-help`" :text="option.help" />
118
161
  </div>
119
162
  </div>
120
163
  </div>
@@ -1,59 +1,74 @@
1
1
  <script setup lang="ts">
2
- import Uniques from "@/helpers/Uniques"
3
- import { computed, useAttrs, useSlots } from "vue"
2
+ import { computed, ref } from "vue"
4
3
  import FieldsetLegend from "./FieldsetLegend.vue"
5
4
  import InputHelp from "./InputHelp.vue"
6
5
  import InputLabel from "./InputLabel.vue"
6
+ import { useInputField, defaultInputProps } from "@/composables/forms"
7
+ import type { OptionsInput, ColumnedInput } from "@/composables/forms"
8
+
9
+ defineOptions({
10
+ inheritAttrs: false,
11
+ })
7
12
 
8
13
  const props = withDefaults(
9
- defineProps<{
10
- options: {
11
- disabled?: boolean
12
- help?: string
13
- label: string
14
- value: string | number
15
- }[]
16
- help?: string
17
- legend?: string
18
- modelValue?: string | number
19
- columns?: 2 | 3 | 4
20
- }>(),
21
- {
22
- help: "",
23
- legend: "",
24
- modelValue: undefined,
25
- columns: undefined,
26
- }
14
+ defineProps<OptionsInput & ColumnedInput>(),
15
+ defaultInputProps
27
16
  )
28
- const emits = defineEmits(["update:modelValue"])
29
- const attrs = useAttrs()
30
- const slots = useSlots()
31
- const uuid = (attrs.id as string) || Uniques.CreateIdAttribute()
32
- const hasLegend = computed(() => {
33
- return props.legend !== "" || slots.legend !== undefined
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]
34
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
+ }
35
44
  </script>
45
+
36
46
  <template>
37
47
  <fieldset
38
- class="space-y-5"
39
- :aria-labelledby="hasLegend ? `${uuid}-legend` : undefined"
40
- :aria-describedby="help ? `${uuid}-help` : undefined"
48
+ class="space-y-4"
49
+ :aria-labelledby="label ? `${inputID}-legend` : undefined"
50
+ :aria-describedby="help ? `${inputID}-help` : undefined"
41
51
  >
42
- <div v-if="hasLegend || help" class="space-y-0.5">
43
- <FieldsetLegend :id="`${uuid}-legend`">
44
- <div v-if="legend">{{ legend }}</div>
45
- <slot v-if="$slots.legend" name="legend"></slot>
46
- </FieldsetLegend>
47
- <InputHelp :id="`${uuid}-help`" tag="p" :text="help" />
52
+ <div v-if="label">
53
+ <FieldsetLegend
54
+ :id="`${inputID}-legend`"
55
+ :label="label"
56
+ :required="isRequired"
57
+ />
58
+ <InputHelp v-if="help" :id="`${inputID}-help`" tag="p" :text="help" />
59
+ </div>
60
+
61
+ <div v-if="errorState" class="mt-0.5">
62
+ <p class="text-sm text-red-700">{{ errorState }}</p>
48
63
  </div>
64
+
49
65
  <div class="flex">
50
66
  <div
51
- class="grid gap-4"
67
+ class="grid gap-y-6"
52
68
  :class="{
53
- 'sm:grid sm:gap-y-4 sm:gap-x-5 sm:space-y-0': columns !== undefined,
69
+ 'sm:grid sm:gap-x-5 sm:space-y-0': columns !== undefined,
54
70
  'sm:grid-cols-2': columns === 2,
55
71
  'sm:grid-cols-3': columns === 3,
56
- 'sm:grid-cols-4': columns === 4,
57
72
  }"
58
73
  >
59
74
  <div
@@ -63,44 +78,42 @@ const hasLegend = computed(() => {
63
78
  >
64
79
  <div class="flex items-center h-5">
65
80
  <input
66
- :id="`${uuid}-${index}`"
81
+ :id="`${inputID}-${index}`"
82
+ ref="radios"
67
83
  :aria-describedby="
68
- option?.help && option.help
69
- ? `${uuid}-${index}-help`
70
- : undefined
84
+ option.help ? `${inputID}-${index}-help` : undefined
71
85
  "
72
- :aria-labelledby="`${uuid}-${index}-label`"
73
- :checked="modelValue === option.value"
74
- class="w-4 h-4 border-gray-600 focus:ring-xy-blue-500 text-xy-blue disabled:opacity-50 disabled:cursor-not-allowed"
75
- :disabled="option.disabled === true ? true : undefined"
76
- :name="uuid"
86
+ :aria-labelledby="`${inputID}-${index}-label`"
87
+ :checked="modelState === option.value"
88
+ :class="[
89
+ 'h-4 w-4 cursor-pointer text-xy-blue',
90
+ 'disabled:bg-gray-100 disabled:border-gray-200 disabled:cursor-not-allowed disabled:opacity-100',
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',
95
+ ]"
96
+ :disabled="option.disabled"
97
+ :name="inputID"
77
98
  type="radio"
78
99
  :value="option.value"
79
- v-bind="{
80
- onChange: () => {
81
- emits('update:modelValue', option.value)
82
- },
83
- ...$attrs,
84
- }"
100
+ v-bind="$attrs"
101
+ @change="onChange($event, option.value)"
102
+ @invalid="onInvalid"
85
103
  />
86
104
  </div>
87
105
  <div class="ml-3">
88
106
  <InputLabel
89
- :id="`${uuid}-${index}-label`"
90
- class="mt-auto"
91
- :disabled="
92
- ($attrs.hasOwnProperty('disabled') &&
93
- $attrs.disabled !== false) ||
94
- option.disabled === true
95
- "
96
- :for="`${uuid}-${index}`"
107
+ :id="`${inputID}-${index}-label`"
108
+ :for="`${inputID}-${index}`"
97
109
  :label="option.label"
110
+ :class="
111
+ isDisabled || option.disabled
112
+ ? 'cursor-not-allowed'
113
+ : 'cursor-pointer'
114
+ "
98
115
  />
99
- <InputHelp
100
- :id="`${uuid}-${index}-help`"
101
- class="-mt-1"
102
- :text="option.help"
103
- />
116
+ <InputHelp :id="`${inputID}-${index}-help`" :text="option.help" />
104
117
  </div>
105
118
  </div>
106
119
  </div>
@@ -1,5 +1,4 @@
1
- <script setup lang="ts">
2
- import Uniques from "@/helpers/Uniques"
1
+ <script setup lang="ts" generic="T extends InputOption">
3
2
  import {
4
3
  RadioGroup,
5
4
  RadioGroupDescription,
@@ -7,95 +6,82 @@ import {
7
6
  RadioGroupOption,
8
7
  } from "@headlessui/vue"
9
8
  import { CheckCircleIcon } from "@heroicons/vue/solid"
10
- import { computed, ref, useAttrs } from "vue"
11
9
  import InputLabel from "./InputLabel.vue"
12
10
  import InputHelp from "./InputHelp.vue"
13
11
  import FieldsetLegend from "./FieldsetLegend.vue"
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"
19
+
20
+ defineOptions({
21
+ inheritAttrs: false,
22
+ })
14
23
 
15
24
  /*
16
25
  * NOTE (spk) headless UI introduced a "name" prop that includes a hidden field
17
26
  * to use the modelValue inside of forms. It does not however resolve the issue of
18
27
  * supporting HTML5 form validation, so we'll add our own hidden radio buttons to support both.
19
- *
20
- * The headless technique does include supporting complex modelValues such as objects, which we may
21
- * need in the future. We can revist required validation at that time using a singular hidden checkbox.
22
28
  */
23
29
 
24
- type ModelValue = string | number
25
-
26
- type RadioCard = {
27
- disabled?: boolean
28
- help?: string
29
- label: string
30
- sublabel?: string
31
- value: ModelValue
30
+ interface RadioCards extends OptionsInput {
31
+ options: T[]
32
32
  }
33
33
 
34
34
  const props = withDefaults(
35
- defineProps<{
36
- columns?: 2 | 3
37
- help?: string
38
- legend?: string
39
- modelValue?: ModelValue
40
- options: RadioCard[]
41
- }>(),
42
- {
43
- columns: undefined,
44
- help: "",
45
- legend: "",
46
- modelValue: undefined,
47
- }
35
+ defineProps<RadioCards & ColumnedInput>(),
36
+ defaultInputProps
48
37
  )
49
38
 
50
- const emit = defineEmits<{
51
- (e: "update:modelValue", modelValue: ModelValue): void
52
- }>()
53
-
54
- const attrs = useAttrs()
55
- const uuid = Uniques.CreateIdAttribute()
56
-
57
- // tracking internal state separate from modelValue
58
- // allows v-model to be undefined by the consumer but still supports
59
- // the display requirements of the component.
60
- // this is usful when the component is used inside a form element and
61
- // tracking v-model isn't required.
62
- const internalState = ref()
63
- const invalid = ref<boolean>()
64
- const checkedState = computed(() => {
65
- if (props.modelValue === undefined) {
66
- 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
67
46
  }
68
47
 
69
- return props.modelValue
48
+ return hiddenRadios.value[0]
70
49
  })
50
+ const {
51
+ inputID,
52
+ isDisabled,
53
+ isRequired,
54
+ nameAttr,
55
+ modelState,
56
+ errorState,
57
+ onInvalid,
58
+ } = useInputField({ props, targetInput })
71
59
 
72
- const onChange = (val: ModelValue) => {
73
- internalState.value = val
74
- invalid.value = false
75
- emit("update:modelValue", val)
60
+ const onUpdate = (val: unknown) => {
61
+ if (val) {
62
+ errorState.value = ""
63
+ }
76
64
  }
77
-
78
- const nameAttr = computed(() => {
79
- return typeof attrs.name === "string" && attrs.name !== "" ? attrs.name : uuid
80
- })
81
65
  </script>
82
66
 
83
67
  <template>
84
68
  <RadioGroup
85
- :model-value="checkedState"
86
- :disabled="typeof attrs.disabled === 'boolean' ? attrs.disabled : false"
87
- :aria-invalid="invalid === true ? 'true' : null"
88
- :aria-errormessage="invalid === true ? `error-${uuid}` : null"
89
- @update:model-value="onChange"
69
+ v-model="modelState"
70
+ :disabled="isDisabled"
71
+ :aria-invalid="errorState ? 'true' : null"
72
+ :aria-errormessage="errorState ? `error-${inputID}` : null"
73
+ @update:model-value="onUpdate"
90
74
  >
91
- <RadioGroupLabel v-if="legend" class="block">
92
- <FieldsetLegend tag="div">{{ legend }}</FieldsetLegend>
75
+ <RadioGroupLabel v-if="label" class="block">
76
+ <FieldsetLegend tag="div" :label="label" :required="isRequired" />
93
77
  </RadioGroupLabel>
78
+
94
79
  <RadioGroupDescription v-if="help">
95
80
  <InputHelp :text="help" />
96
81
  </RadioGroupDescription>
97
- <div v-if="invalid === true" :id="`error-${uuid}`" class="sr-only">
98
- Please select one of these options.
82
+
83
+ <div v-if="errorState" :id="`error-${inputID}`">
84
+ <p class="text-sm text-red-700">{{ errorState }}</p>
99
85
  </div>
100
86
 
101
87
  <div
@@ -108,17 +94,28 @@ const nameAttr = computed(() => {
108
94
  <RadioGroupOption
109
95
  v-for="option in options"
110
96
  :key="option.value"
111
- v-slot="{ active, checked, disabled }"
97
+ v-slot="{
98
+ active,
99
+ checked,
100
+ disabled,
101
+ }: {
102
+ active: boolean,
103
+ checked: boolean,
104
+ disabled: boolean,
105
+ }"
112
106
  as="template"
113
- :disabled="option?.disabled ? option.disabled : false"
107
+ :disabled="isDisabled || option.disabled"
114
108
  :value="option.value"
115
109
  >
116
110
  <div
117
- class="relative bg-white border rounded-lg shadow-sm p-4 flex cursor-pointer focus:outline-none"
111
+ class="relative border rounded-lg shadow-sm p-4 flex focus:outline-none"
118
112
  :class="[
119
- checked ? 'border-transparent' : 'border-gray-300',
113
+ disabled
114
+ ? 'cursor-not-allowed bg-gray-50 border-gray-200 opacity-90'
115
+ : 'cursor-pointer bg-white border-gray-300',
116
+ errorState && !disabled ? 'border-red-700' : '',
117
+ checked ? 'border-transparent' : '',
120
118
  active ? 'border-xy-blue ring-2 ring-xy-blue-500' : '',
121
- disabled ? 'cursor-not-allowed opacity-75' : '',
122
119
  ]"
123
120
  >
124
121
  <div class="flex-1 flex pr-1">
@@ -130,16 +127,18 @@ const nameAttr = computed(() => {
130
127
  :label="option.label"
131
128
  />
132
129
  </RadioGroupLabel>
130
+
133
131
  <RadioGroupDescription v-if="option.help" as="div">
134
132
  <InputHelp tag="div" class="mt-auto" :text="option.help" />
135
133
  </RadioGroupDescription>
134
+
136
135
  <div
137
136
  v-if="option.sublabel || $slots.sublabel"
138
137
  class="mt-auto mb-0"
139
138
  >
140
139
  <RadioGroupDescription
141
140
  as="div"
142
- class="font-semibold leading-snug mt-4 text-gray-900 text-sm"
141
+ class="text-sm/5 font-medium mt-4 text-gray-800"
143
142
  >
144
143
  <slot
145
144
  name="sublabel"
@@ -165,16 +164,19 @@ const nameAttr = computed(() => {
165
164
  ]"
166
165
  aria-hidden="true"
167
166
  />
167
+
168
+ <!--TODO: (spk) ideally this would trigger a change event -->
168
169
  <input
170
+ ref="hiddenRadios"
169
171
  class="sr-only top-1 left-1"
170
172
  aria-hidden="true"
171
173
  :checked="checked"
172
174
  :name="nameAttr"
173
- :required="attrs.required !== undefined && attrs.required !== false"
175
+ :required="isRequired"
174
176
  tabindex="-1"
175
177
  type="radio"
176
178
  :value="option.value"
177
- @invalid="invalid = true"
179
+ @invalid="onInvalid"
178
180
  />
179
181
  </div>
180
182
  </RadioGroupOption>