mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-12 16:40:05 +00:00
## Summary Surfaces the `label_on` and `label_off` (aka `on`/`off`) props for BOOLEAN widgets in the Vue implementation using a new Reka UI ToggleGroup component. **Before:** Labels extended outside node boundaries, causing overflow issues. **After:** Labels truncate with ellipsis and share space equally within the widget. [Screencast from 2026-02-13 16-27-49.webm](https://github.com/user-attachments/assets/912bab39-50a0-4d4e-8046-4b982e145bd9) ## Changes ### New ToggleGroup Component (`src/components/ui/toggle-group/`) - `ToggleGroup.vue` - Wrapper around Reka UI `ToggleGroupRoot` with variant support - `ToggleGroupItem.vue` - Styled toggle items with size variants (sm/default/lg) - `toggleGroup.variants.ts` - CVA variants adapted to ComfyUI design tokens - `ToggleGroup.stories.ts` - Storybook stories (Default, Disabled, Outline, Sizes, LongLabels) ### WidgetToggleSwitch Updates - Conditionally renders `ToggleGroup` when `options.on` or `options.off` are provided - Keeps PrimeVue `ToggleSwitch` for implicit boolean states (no labels) - Items use `flex-1 min-w-0 truncate` to share space and handle overflow ### i18n - Added `widgets.boolean.true` and `widgets.boolean.false` translation keys for fallback labels ## Implementation Details Follows the established pattern for adding Reka UI components in this codebase: - Uses Reka UI primitives (`ToggleGroupRoot`, `ToggleGroupItem`) - Adapts shadcn-vue structure with ComfyUI design tokens - Reactive provide/inject with typed `InjectionKey` for variant context - Styling matches existing `FormSelectButton` appearance Fixes COM-12709 --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
183 lines
5.3 KiB
TypeScript
183 lines
5.3 KiB
TypeScript
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
import { ref } from 'vue'
|
|
|
|
import ToggleGroup from './ToggleGroup.vue'
|
|
import ToggleGroupItem from './ToggleGroupItem.vue'
|
|
|
|
const meta = {
|
|
title: 'Components/ToggleGroup',
|
|
component: ToggleGroup,
|
|
tags: ['autodocs'],
|
|
argTypes: {
|
|
type: {
|
|
control: 'select',
|
|
options: ['single', 'multiple'],
|
|
description: 'Single or multiple selection'
|
|
},
|
|
variant: {
|
|
control: 'select',
|
|
options: ['default', 'outline'],
|
|
description: 'Visual style variant'
|
|
},
|
|
disabled: {
|
|
control: 'boolean',
|
|
description: 'When true, disables the toggle group'
|
|
},
|
|
'onUpdate:modelValue': { action: 'update:modelValue' }
|
|
}
|
|
} satisfies Meta<typeof ToggleGroup>
|
|
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
export const Default: Story = {
|
|
render: (args) => ({
|
|
components: { ToggleGroup, ToggleGroupItem },
|
|
setup() {
|
|
const value = ref('center')
|
|
return { value, args }
|
|
},
|
|
template: `
|
|
<ToggleGroup
|
|
v-model="value"
|
|
type="single"
|
|
:variant="args.variant"
|
|
:disabled="args.disabled"
|
|
class="border border-border-default rounded-lg p-1"
|
|
>
|
|
<ToggleGroupItem value="left">Left</ToggleGroupItem>
|
|
<ToggleGroupItem value="center">Center</ToggleGroupItem>
|
|
<ToggleGroupItem value="right">Right</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
<div class="mt-4 text-sm text-muted-foreground">
|
|
Selected: {{ value || 'None' }}
|
|
</div>
|
|
`
|
|
}),
|
|
args: {
|
|
variant: 'default',
|
|
disabled: false
|
|
}
|
|
}
|
|
|
|
export const Disabled: Story = {
|
|
render: (args) => ({
|
|
components: { ToggleGroup, ToggleGroupItem },
|
|
setup() {
|
|
const value = ref('on')
|
|
return { value, args }
|
|
},
|
|
template: `
|
|
<ToggleGroup v-model="value" type="single" disabled class="border border-border-default rounded-lg p-1">
|
|
<ToggleGroupItem value="off">Off</ToggleGroupItem>
|
|
<ToggleGroupItem value="on">On</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
`
|
|
}),
|
|
args: {}
|
|
}
|
|
|
|
export const OutlineVariant: Story = {
|
|
render: (args) => ({
|
|
components: { ToggleGroup, ToggleGroupItem },
|
|
setup() {
|
|
const value = ref('medium')
|
|
return { value, args }
|
|
},
|
|
template: `
|
|
<ToggleGroup v-model="value" type="single" variant="outline" class="gap-2">
|
|
<ToggleGroupItem value="small" size="sm">Small</ToggleGroupItem>
|
|
<ToggleGroupItem value="medium">Medium</ToggleGroupItem>
|
|
<ToggleGroupItem value="large" size="lg">Large</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
`
|
|
}),
|
|
args: {}
|
|
}
|
|
|
|
export const BooleanToggle: Story = {
|
|
render: (args) => ({
|
|
components: { ToggleGroup, ToggleGroupItem },
|
|
setup() {
|
|
const value = ref('off')
|
|
return { value, args }
|
|
},
|
|
template: `
|
|
<div class="space-y-4">
|
|
<p class="text-sm text-muted-foreground">Boolean toggle with custom labels:</p>
|
|
<ToggleGroup
|
|
v-model="value"
|
|
type="single"
|
|
class="w-48 border border-border-default rounded-lg p-1"
|
|
>
|
|
<ToggleGroupItem value="off" size="sm">Outside</ToggleGroupItem>
|
|
<ToggleGroupItem value="on" size="sm">Inside</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
<div class="text-sm">Value: {{ value === 'on' ? true : false }}</div>
|
|
</div>
|
|
`
|
|
}),
|
|
args: {}
|
|
}
|
|
|
|
export const LongLabels: Story = {
|
|
render: (args) => ({
|
|
components: { ToggleGroup, ToggleGroupItem },
|
|
setup() {
|
|
const value = ref('option1')
|
|
return { value, args }
|
|
},
|
|
template: `
|
|
<div class="w-64">
|
|
<p class="text-sm text-muted-foreground mb-2">Labels truncate with ellipsis:</p>
|
|
<ToggleGroup
|
|
v-model="value"
|
|
type="single"
|
|
class="border border-border-default rounded-lg p-1"
|
|
>
|
|
<ToggleGroupItem value="option1" size="sm">Very Long Label One</ToggleGroupItem>
|
|
<ToggleGroupItem value="option2" size="sm">Another Long Label</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</div>
|
|
`
|
|
}),
|
|
args: {}
|
|
}
|
|
|
|
export const Sizes: Story = {
|
|
render: () => ({
|
|
components: { ToggleGroup, ToggleGroupItem },
|
|
setup() {
|
|
const sm = ref('a')
|
|
const md = ref('a')
|
|
const lg = ref('a')
|
|
return { sm, md, lg }
|
|
},
|
|
template: `
|
|
<div class="space-y-4">
|
|
<div>
|
|
<p class="text-sm text-muted-foreground mb-2">Small:</p>
|
|
<ToggleGroup v-model="sm" type="single" class="border border-border-default rounded p-1">
|
|
<ToggleGroupItem value="a" size="sm">A</ToggleGroupItem>
|
|
<ToggleGroupItem value="b" size="sm">B</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-muted-foreground mb-2">Default:</p>
|
|
<ToggleGroup v-model="md" type="single" class="border border-border-default rounded-lg p-1">
|
|
<ToggleGroupItem value="a">A</ToggleGroupItem>
|
|
<ToggleGroupItem value="b">B</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-muted-foreground mb-2">Large:</p>
|
|
<ToggleGroup v-model="lg" type="single" class="border border-border-default rounded-lg p-1">
|
|
<ToggleGroupItem value="a" size="lg">A</ToggleGroupItem>
|
|
<ToggleGroupItem value="b" size="lg">B</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</div>
|
|
</div>
|
|
`
|
|
})
|
|
}
|