diff --git a/client/web/compose/src/components/ModuleFields/Configurator/Select.vue b/client/web/compose/src/components/ModuleFields/Configurator/Select.vue index 86b7ffab02..0b1cad687a 100644 --- a/client/web/compose/src/components/ModuleFields/Configurator/Select.vue +++ b/client/web/compose/src/components/ModuleFields/Configurator/Select.vue @@ -25,6 +25,17 @@ + + + + @@ -45,13 +57,27 @@ class="text-primary" style="min-width: 200px;" > - {{ $t('kind.select.optionValuePlaceholder') }} + {{ $t('kind.select.options.value') }} - {{ $t('kind.select.optionLabelPlaceholder') }} + {{ $t('kind.select.options.label') }} + + + + {{ $t('kind.select.options.style.textColor') }} + + + + {{ $t('kind.select.options.style.backgroundColor') }} @@ -80,8 +106,7 @@ @@ -92,8 +117,7 @@ @@ -110,6 +134,39 @@ + + + + + + + + { + const selectOptions = this.selectTypes.map((o) => { if (o.value === 'list') { o.text = this.$t(`kind.select.optionType.${this.f.isMulti ? 'checkbox' : 'radio'}`) } @@ -197,12 +257,31 @@ export default { return selectOptions.filter(({ onlyMulti }) => !onlyMulti) }, + displayOptions () { + return [ + { text: this.$t('kind.select.displayType.text'), value: 'text' }, + { text: this.$t('kind.select.displayType.badge'), value: 'badge' }, + ] + }, + shouldAllowDuplicates () { if (!this.f.isMulti) return false - const { allowDuplicates } = this.options.find(({ value }) => value === this.f.options.selectType) || {} + const { allowDuplicates } = this.selectTypes.find(({ value }) => value === this.f.options.selectType) || {} return !!allowDuplicates }, + + themeSettings () { + return this.$Settings.get('ui.studio.themes', []) + }, + + defaultTextColor () { + return getComputedStyle(document.documentElement).getPropertyValue('--dark') + }, + + defaultBackgroundColor () { + return getComputedStyle(document.documentElement).getPropertyValue('--extra-light') + }, }, created () { @@ -219,15 +298,14 @@ export default { methods: { handleAddOption () { - this.f.options.options.push({ - value: undefined, - text: undefined, - new: true, - }) + const option = this.f.createSelectOption() + option.new = true + + this.f.options.options.push(option) }, updateIsUniqueMultiValue (value) { - const { allowDuplicates = false } = this.options.find(({ value: v }) => v === value) || {} + const { allowDuplicates = false } = this.selectTypes.find(({ value: v }) => v === value) || {} if (!allowDuplicates) { this.f.options.isUniqueMultiValue = true } @@ -235,7 +313,7 @@ export default { setDefaultValues () { this.newOption = {} - this.options = [] + this.selectTypes = [] }, }, } diff --git a/client/web/compose/src/components/ModuleFields/Editor/Select.vue b/client/web/compose/src/components/ModuleFields/Editor/Select.vue index a7bdb50473..96397b6dd2 100644 --- a/client/web/compose/src/components/ModuleFields/Editor/Select.vue +++ b/client/web/compose/src/components/ModuleFields/Editor/Select.vue @@ -34,11 +34,23 @@ @@ -82,10 +96,17 @@ :placeholder="$t('kind.select.placeholder')" :selectable="isSelectable" label="text" + :badge="field.options.displayType === 'badge'" @input="setMultiValue($event, ctx.index)" /> - {{ findLabel(value[ctx.index]) }} + + {{ findLabel(value[ctx.index]) }} + @@ -101,6 +122,7 @@ :reduce="o => o.value" :selectable="isSelectable" label="text" + :badge="field.options.displayType === 'badge'" /> value === v) || { style: {} } + + style.fontSize = '0.9rem' + style.color = opt.style.textColor || 'var(--dark)' + style.backgroundColor = opt.style.backgroundColor || 'var(--extra-light)' + } + + return style + }, }, } diff --git a/client/web/compose/src/components/ModuleFields/Editor/multi.vue b/client/web/compose/src/components/ModuleFields/Editor/multi.vue index 6352533e6c..d32582cca3 100644 --- a/client/web/compose/src/components/ModuleFields/Editor/multi.vue +++ b/client/web/compose/src/components/ModuleFields/Editor/multi.vue @@ -15,7 +15,7 @@
+
+ + + {{ v.text }} + + + {{ index !== value.length - 1 ? field.options.multiDelimiter : '' }} + + + +
+ + diff --git a/lib/js/src/compose/types/module-field/select.ts b/lib/js/src/compose/types/module-field/select.ts index a1facd0bc1..11d524787b 100644 --- a/lib/js/src/compose/types/module-field/select.ts +++ b/lib/js/src/compose/types/module-field/select.ts @@ -1,14 +1,24 @@ -// @todo option to allow multiple entries -// @todo option to allow duplicates import { ModuleField, Registry, Options, defaultOptions } from './base' import { Apply } from '../../../cast' import { AreStrings } from '../../../guards' const kind = 'Select' +interface SelectOptionStyle { + textColor?: string + backgroundColor?: string +} + +interface SelectOption { + value: string; + text: string; + style: SelectOptionStyle; +} + interface SelectOptions extends Options { - options: Array; + options: Array; selectType: string; + displayType: 'text' | 'badge'; multiDelimiter: string; isUniqueMultiValue: boolean; } @@ -19,6 +29,7 @@ const defaults = (): Readonly => Object.freeze({ selectType: 'default', multiDelimiter: '\n', isUniqueMultiValue: false, + displayType: 'text', }) export class ModuleFieldSelect extends ModuleField { @@ -35,23 +46,33 @@ export class ModuleFieldSelect extends ModuleField { if (!o) return super.applyOptions(o) - Apply(this.options, o, String, 'selectType', 'multiDelimiter') + Apply(this.options, o, String, 'selectType', 'multiDelimiter', 'displayType') Apply(this.options, o, Boolean, 'isUniqueMultiValue') if (o.options) { - let opt: Array<{ value: string; text: string }> = [] + let opt: Array = [] if (AreStrings(o.options)) { - opt = o.options - .map(value => ({ value, text: value })) + opt = o.options.map((value: string) => this.createSelectOption({ value, text: value })) } else { - opt = (o.options as Array<{ value: string; text?: string }>) - .map(({ value, text }) => ({ value, text: text || value })) + opt = o.options.map(o => this.createSelectOption(o)) } this.options.options = opt } } + + createSelectOption ({ value = '', text = '', style = {} }: Partial = {}): SelectOption { + const { textColor = '', backgroundColor = '' } = style || {} + return { + value, + text, + style: { + textColor, + backgroundColor, + }, + } + } } Registry.set(kind, ModuleFieldSelect) diff --git a/lib/vue/src/components/input/CInputColorPicker.vue b/lib/vue/src/components/input/CInputColorPicker.vue index a64c3ccc3c..5f1fef7b93 100644 --- a/lib/vue/src/components/input/CInputColorPicker.vue +++ b/lib/vue/src/components/input/CInputColorPicker.vue @@ -180,6 +180,10 @@ export default { key: 'white', label: 'White', }, + { + key: 'black', + label: 'Black', + }, { key: 'primary', label: 'Primary', @@ -237,6 +241,10 @@ export default { immediate: true, handler (value) { this.currentColor = value + + if (!value && this.defaultValue) { + this.$emit('input', this.defaultValue) + } }, }, }, diff --git a/lib/vue/src/components/input/CInputSelect.vue b/lib/vue/src/components/input/CInputSelect.vue index e2ddc61839..ebafae8af7 100644 --- a/lib/vue/src/components/input/CInputSelect.vue +++ b/lib/vue/src/components/input/CInputSelect.vue @@ -1,8 +1,8 @@ @@ -98,6 +140,11 @@ export default { type: Boolean, default: false, }, + + badge: { + type: Boolean, + default: false, + }, }, data () { @@ -115,7 +162,7 @@ export default { set (v) { this.$emit('input', !v ? this.defaultValue : v) - } + }, }, sizeClass () { @@ -182,6 +229,16 @@ export default { this.$emit('search', query, loading) }, + + getOptionStyle ({ style = {} } = {}) { + if (this.badge) { + style.fontSize = '0.9rem' + style.color = style.textColor || 'var(--dark)' + style.backgroundColor = style.backgroundColor || 'var(--extra-light)' + } + + return style + }, }, } @@ -318,9 +375,8 @@ export default { } &.vs__dropdown-option--disabled { - background: var(--vs-state-disabled-bg) !important; - color: var(--vs-state-disabled-color) !important; cursor: var(--vs-state-disabled-cursor) !important; + opacity: 0.5; } &:active { diff --git a/locale/en/corteza-webapp-compose/field.yaml b/locale/en/corteza-webapp-compose/field.yaml index 551f6c519a..93a8a0fe9c 100644 --- a/locale/en/corteza-webapp-compose/field.yaml +++ b/locale/en/corteza-webapp-compose/field.yaml @@ -134,7 +134,12 @@ kind: allow-duplicates: Allow duplicates label: Select optionAdd: Add - optionLabelPlaceholder: Label + options: + value: Value + label: Label + style: + textColor: Text color + backgroundColor: Background color optionNotSelected: No option selected optionRemove: Remove optionType: @@ -144,8 +149,12 @@ kind: multiple: Multiple select checkbox: Checkbox Group radio: Radio Group - optionValuePlaceholder: Value + displayType: + label: Display value as + text: Text + badge: Badge optionsLabel: Options to select from + showAsBadge: Show as badge placeholder: Select an option by clicking here string: label: String diff --git a/locale/en/corteza-webapp-compose/general.yaml b/locale/en/corteza-webapp-compose/general.yaml index bc8208be02..1423bda622 100644 --- a/locale/en/corteza-webapp-compose/general.yaml +++ b/locale/en/corteza-webapp-compose/general.yaml @@ -22,6 +22,7 @@ label: close: Close create: Create delete: Delete + default: Default restore: Restore descending: Descending description: Description diff --git a/server/compose/types/module_field.go b/server/compose/types/module_field.go index 1049199657..5dbde8f7f9 100644 --- a/server/compose/types/module_field.go +++ b/server/compose/types/module_field.go @@ -248,7 +248,14 @@ func (f *ModuleField) decodeTranslationsMetaOptionsValueText(tt locale.ResourceT } for i, optUnknown := range optsSlice { - outOpt := map[string]string{} + outOpt := map[string]any{ + "value": "", + "text": "", + "style": map[string]any{ + "textColor": "", + "backgroundColor": "", + }, + } // what is this we're dealing with? slice of strings (values) or map (value+text) switch opt := optUnknown.(type) { @@ -266,12 +273,16 @@ func (f *ModuleField) decodeTranslationsMetaOptionsValueText(tt locale.ResourceT } outOpt["text"], _ = opt["text"].(string) + + if style, ok := opt["style"].(map[string]any); ok { + outOpt["style"] = style + } } // find the translation for that value // and update the option (effectively overwriting // the original text value (in case of map option) - trKey := strings.NewReplacer("{{value}}", outOpt["value"]).Replace(LocaleKeyModuleFieldMetaOptionsValueText.Path) + trKey := strings.NewReplacer("{{value}}", outOpt["value"].(string)).Replace(LocaleKeyModuleFieldMetaOptionsValueText.Path) if tr = tt.FindByKey(trKey); tr != nil { outOpt["text"] = tr.Msg }