diff --git a/src/components/ui/toggle-group/ToggleGroup.vue b/src/components/ui/toggle-group/ToggleGroup.vue new file mode 100644 index 0000000000..48c0ff34bc --- /dev/null +++ b/src/components/ui/toggle-group/ToggleGroup.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/ui/toggle-group/ToggleGroupItem.vue b/src/components/ui/toggle-group/ToggleGroupItem.vue new file mode 100644 index 0000000000..3f75a62d2d --- /dev/null +++ b/src/components/ui/toggle-group/ToggleGroupItem.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/components/ui/toggle-group/toggleGroup.variants.ts b/src/components/ui/toggle-group/toggleGroup.variants.ts new file mode 100644 index 0000000000..fbd7f47779 --- /dev/null +++ b/src/components/ui/toggle-group/toggleGroup.variants.ts @@ -0,0 +1,36 @@ +import type { VariantProps } from 'cva' +import { cva } from 'cva' + +export const toggleGroupVariants = cva({ + base: 'flex gap-[var(--primitive-padding-padding-1,4px)] p-[var(--primitive-padding-padding-1,4px)] rounded-[var(--primitive-border-radius-rounded-sm,4px)] bg-component-node-widget-background' +}) + +export const toggleGroupItemVariants = cva({ + base: 'flex-1 inline-flex items-center justify-center border-0 rounded-[var(--primitive-border-radius-rounded-sm,4px)] px-[var(--primitive-padding-padding-2,8px)] py-[var(--primitive-padding-padding-1,4px)] text-xs font-inter font-normal transition-colors cursor-pointer overflow-hidden', + variants: { + variant: { + primary: [ + 'data-[state=off]:bg-transparent data-[state=off]:text-muted-foreground', + 'data-[state=off]:hover:bg-component-node-widget-background-hovered data-[state=off]:hover:text-white', + 'data-[state=on]:bg-primary-background data-[state=on]:text-white' + ], + secondary: [ + 'data-[state=off]:bg-transparent data-[state=off]:text-muted-foreground', + 'data-[state=off]:hover:bg-component-node-widget-background-hovered data-[state=off]:hover:text-white', + 'data-[state=on]:bg-component-node-widget-background-selected data-[state=on]:text-base-foreground' + ], + inverted: [ + 'data-[state=off]:bg-transparent data-[state=off]:text-muted-foreground', + 'data-[state=off]:hover:bg-component-node-widget-background-hovered data-[state=off]:hover:text-white', + 'data-[state=on]:bg-white data-[state=on]:text-base-background' + ] + } + }, + defaultVariants: { + variant: 'secondary' + } +}) + +export type ToggleGroupItemVariants = VariantProps< + typeof toggleGroupItemVariants +> diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 4b006d3605..f0e59e7468 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -2051,6 +2051,10 @@ "Set Group Nodes to Always": "Set Group Nodes to Always" }, "widgets": { + "boolean": { + "true": "true", + "false": "false" + }, "selectModel": "Select model", "uploadSelect": { "placeholder": "Select...", diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.test.ts index 7e7a876484..de91c436ed 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.test.ts @@ -1,17 +1,30 @@ import { mount } from '@vue/test-utils' import PrimeVue from 'primevue/config' import ToggleSwitch from 'primevue/toggleswitch' -import type { ToggleSwitchProps } from 'primevue/toggleswitch' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' +import ToggleGroup from '@/components/ui/toggle-group/ToggleGroup.vue' +import ToggleGroupItem from '@/components/ui/toggle-group/ToggleGroupItem.vue' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetToggleSwitch from './WidgetToggleSwitch.vue' +vi.mock('vue-i18n', () => ({ + useI18n: () => ({ + t: (key: string) => { + const translations: Record = { + 'widgets.boolean.true': 'true', + 'widgets.boolean.false': 'false' + } + return translations[key] || key + } + }) +})) + describe('WidgetToggleSwitch Value Binding', () => { const createMockWidget = ( value: boolean = false, - options: Partial = {}, + options: Record = {}, callback?: (value: boolean) => void ): SimplifiedWidget => ({ name: 'test_toggle', @@ -34,7 +47,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }, global: { plugins: [PrimeVue], - components: { ToggleSwitch } + components: { ToggleSwitch, ToggleGroup, ToggleGroupItem } } }) } @@ -149,4 +162,82 @@ describe('WidgetToggleSwitch Value Binding', () => { expect(emitted![3]).toContain(false) }) }) + + describe('Label Display', () => { + it('uses ToggleGroup when labels are provided', () => { + const widget = createMockWidget(false, { on: 'Enabled', off: 'Disabled' }) + const wrapper = mountComponent(widget, false) + + expect(wrapper.findComponent({ name: 'ToggleGroup' }).exists()).toBe(true) + expect(wrapper.findComponent({ name: 'ToggleSwitch' }).exists()).toBe( + false + ) + }) + + it('uses ToggleSwitch when no labels are provided', () => { + const widget = createMockWidget(false, {}) + const wrapper = mountComponent(widget, false) + + expect(wrapper.findComponent({ name: 'ToggleSwitch' }).exists()).toBe( + true + ) + expect(wrapper.findComponent({ name: 'ToggleGroup' }).exists()).toBe( + false + ) + }) + + it('displays both label_on and label_off in ToggleGroup', () => { + const widget = createMockWidget(false, { on: 'Enabled', off: 'Disabled' }) + const wrapper = mountComponent(widget, false) + + expect(wrapper.text()).toContain('Enabled') + expect(wrapper.text()).toContain('Disabled') + }) + + it('displays correct active state for false', () => { + const widget = createMockWidget(false, { on: 'Enabled', off: 'Disabled' }) + const wrapper = mountComponent(widget, false) + + const toggleGroup = wrapper.findComponent({ name: 'ToggleGroup' }) + expect(toggleGroup.props('modelValue')).toBe('off') + }) + + it('displays correct active state for true', () => { + const widget = createMockWidget(true, { on: 'Enabled', off: 'Disabled' }) + const wrapper = mountComponent(widget, true) + + const toggleGroup = wrapper.findComponent({ name: 'ToggleGroup' }) + expect(toggleGroup.props('modelValue')).toBe('on') + }) + + it('updates active state when toggled', async () => { + const widget = createMockWidget(false, { + on: 'Markdown', + off: 'Plaintext' + }) + const wrapper = mountComponent(widget, false) + + const toggleGroup = wrapper.findComponent({ name: 'ToggleGroup' }) + expect(toggleGroup.props('modelValue')).toBe('off') + + await wrapper.setProps({ modelValue: true }) + + expect(toggleGroup.props('modelValue')).toBe('on') + }) + + it('emits update:modelValue when ToggleGroup item is clicked', async () => { + const widget = createMockWidget(false, { + on: 'Markdown', + off: 'Plaintext' + }) + const wrapper = mountComponent(widget, false) + + const toggleGroup = wrapper.findComponent({ name: 'ToggleGroup' }) + await toggleGroup.vm.$emit('update:modelValue', 'on') + + const emitted = wrapper.emitted('update:modelValue') + expect(emitted).toBeDefined() + expect(emitted![0]).toContain(true) + }) + }) }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.vue index b902b6655e..58b9cc75be 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.vue @@ -1,6 +1,27 @@