From c4ff2dfb4b231685c8c432591ae99f14dddab15a Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Tue, 5 May 2026 13:14:54 -0500 Subject: [PATCH 1/6] Remove unused Combobox component --- resources/js/components/Combobox.vue | 101 --------------------------- 1 file changed, 101 deletions(-) delete mode 100644 resources/js/components/Combobox.vue diff --git a/resources/js/components/Combobox.vue b/resources/js/components/Combobox.vue deleted file mode 100644 index 79f8c3ca71c..00000000000 --- a/resources/js/components/Combobox.vue +++ /dev/null @@ -1,101 +0,0 @@ - - - - - From 7343e3752dc2d86dc576c79674474246cb932b2f Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Tue, 5 May 2026 13:15:20 -0500 Subject: [PATCH 2/6] Fix timzezone input --- resources/js/pages/SettingsGeneralPage.vue | 54 +++++++++------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/resources/js/pages/SettingsGeneralPage.vue b/resources/js/pages/SettingsGeneralPage.vue index 9f81c979609..de7e9e66fb1 100644 --- a/resources/js/pages/SettingsGeneralPage.vue +++ b/resources/js/pages/SettingsGeneralPage.vue @@ -10,6 +10,7 @@ import {useEventListener} from '@vueuse/core'; import type {SelectOption, SuggestionGroup} from '@/types'; import CalloutReadOnly from '@/components/CalloutReadOnly.vue'; + import CraftCombobox from '@/components/form/CraftCombobox.vue'; const props = defineProps<{ readOnly?: boolean; @@ -249,43 +250,34 @@ - - - {{ timezone.label - }}{{ timezone.data?.hint ? ` — ${timezone.data.hint}` : '' }} - - - This can be set to an environment variable with a value of a - supported time zone. - -
    -
  • {{ errors.timeZone }}
  • -
-
+ + From edd5ba096b4a7dd5c681ecc72f83bf61dc5b93f5 Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Tue, 5 May 2026 13:56:52 -0500 Subject: [PATCH 3/6] Fix timezone settings --- .../js/components/form/CraftCombobox.vue | 29 ++-- .../js/components/form/InputCombobox.vue | 6 +- .../components/form/InputComboboxOption.vue | 23 ++- resources/js/pages/SettingsGeneralPage.vue | 154 +++++++----------- resources/js/utils/transformBooleanOptions.ts | 48 ++++++ .../Settings/GeneralSettingsController.php | 43 +++-- .../GeneralSettingsControllerTest.php | 2 +- 7 files changed, 163 insertions(+), 142 deletions(-) create mode 100644 resources/js/utils/transformBooleanOptions.ts diff --git a/resources/js/components/form/CraftCombobox.vue b/resources/js/components/form/CraftCombobox.vue index 4d68cc445fe..f24ac9b1130 100644 --- a/resources/js/components/form/CraftCombobox.vue +++ b/resources/js/components/form/CraftCombobox.vue @@ -2,13 +2,10 @@ import {t} from '@craftcms/cp'; import InputCombobox from '@/components/form/InputCombobox.vue'; import type {SelectItem} from '@/types'; - import {computed} from 'vue'; + import {computed, useSlots} from 'vue'; - const emit = defineEmits<{ - (e: 'update:modelValue', value: string): void; - }>(); + const modelValue = defineModel(); const props = defineProps<{ - modelValue: string; label: string; id?: string; name?: string; @@ -16,15 +13,13 @@ options?: Array; callouts?: Array; error?: string; + requireOptionMatch?: boolean; }>(); - const valueProxy = computed({ - get() { - return props.modelValue; - }, - set(newValue) { - emit('update:modelValue', newValue); - }, + const slots = useSlots(); + const forwardedSlots = computed(() => { + const {default: _, ...rest} = slots; + return rest; }); @@ -35,14 +30,20 @@ :name="name" :disabled="disabled" :has-feedback-for="error ? 'error' : ''" + :require-options-match="requireOptionMatch" v-bind="$attrs" > + > + + +
diff --git a/resources/js/components/form/InputCombobox.vue b/resources/js/components/form/InputCombobox.vue index 8f86358c022..c632ff9dcc7 100644 --- a/resources/js/components/form/InputCombobox.vue +++ b/resources/js/components/form/InputCombobox.vue @@ -18,13 +18,13 @@ import InputComboboxOption from '@/components/form/InputComboboxOption.vue'; const emit = defineEmits<{ - (e: 'update:modelValue', value: string): void; + (e: 'update:modelValue', value: string | number): void; }>(); const props = withDefaults( defineProps<{ label?: string; options?: Array; - modelValue?: string; + modelValue?: string | number | boolean; requireOptionMatch?: boolean; transformModelValue?: (newValue: SelectOption | null) => string; class?: HTMLAttributes['class']; @@ -93,7 +93,7 @@ return ( option.label.toLowerCase().includes(lowerQuery) || - option.value.toLowerCase().includes(lowerQuery) || + option.value.toString().toLowerCase().includes(lowerQuery) || (option.data?.keywords?.toLowerCase().includes(lowerQuery) ?? false) ); } diff --git a/resources/js/components/form/InputComboboxOption.vue b/resources/js/components/form/InputComboboxOption.vue index 1518f08b1fd..50965173dd3 100644 --- a/resources/js/components/form/InputComboboxOption.vue +++ b/resources/js/components/form/InputComboboxOption.vue @@ -15,16 +15,21 @@ :checked="selected" :hint="option.data?.hint" > - +
diff --git a/resources/js/pages/SettingsGeneralPage.vue b/resources/js/pages/SettingsGeneralPage.vue index de7e9e66fb1..b78e3c77872 100644 --- a/resources/js/pages/SettingsGeneralPage.vue +++ b/resources/js/pages/SettingsGeneralPage.vue @@ -8,39 +8,32 @@ import TransitionFade from '@/components/TransitionFade.vue'; import {computed} from 'vue'; import {useEventListener} from '@vueuse/core'; - import type {SelectOption, SuggestionGroup} from '@/types'; + import type {SelectOption} from '@/types'; import CalloutReadOnly from '@/components/CalloutReadOnly.vue'; import CraftCombobox from '@/components/form/CraftCombobox.vue'; + import CraftInput from '@craftcms/cp/vue/CraftInput.vue'; + import {transformBooleanOptions} from '@/utils/transformBooleanOptions'; const props = defineProps<{ - readOnly?: boolean; system: SystemData; - nameSuggestions?: Array; + nameSuggestions?: Array; timezoneOptions?: Array; systemStatusOptions?: Array; - saveUrl: string; flash?: Record; errors: Record; }>(); const flash = computed(() => props.flash); const errors = computed(() => props.errors); + const {readOnly} = useCraftData(); const form = useForm({ - name: props.system.name, + name: props.system.name ?? '', live: props.system.live, retryDuration: props.system.retryDuration, timeZone: props.system.timeZone, }); - function handleUpdate(event: CustomEvent) { - const target = event.target as HTMLSelectElement & {modelValue: string}; - if (target) { - // @ts-ignore we're just going to trust that `name` is a key of `form` for now - form[target.name] = target.modelValue; - } - } - // Handle cmd + s events useEventListener('keydown', (event) => { if ((event.metaKey || event.ctrlKey) && event.key === 's') { @@ -49,6 +42,29 @@ } }); + const statusOptions = computed(() => { + return [ + { + label: t('Online'), + value: true, + data: { + indicator: {variant: 'success'}, + }, + }, + { + label: t('Offline'), + value: false, + data: { + indicator: {variant: 'empty'}, + }, + }, + ...transformBooleanOptions(props.systemStatusOptions ?? [], { + trueLabel: t('Online'), + falseLabel: t('Offline'), + }), + ]; + }); + function save() { form.clearErrors().submit(store()); } @@ -124,26 +140,19 @@ - - -
+ + - - -
- - {{ t('Online') }} -
-
- -
- - {{ t('Offline') }} -
-
- -