diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c84779ff..3db06ce80 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -243,7 +243,7 @@ pnpm format ### Styling - Use Tailwind CSS classes instead of custom CSS -- Follow the existing dark theme pattern: `dark-theme:` prefix (not `dark:`) +- NEVER use `dark:` or `dark-theme:` tailwind variants. Instead use a semantic value from the `style.css` theme, e.g. `bg-node-component-surface` ### Internationalization - All user-facing strings must use vue-i18n diff --git a/browser_tests/tests/chatHistory.spec.ts b/browser_tests/tests/chatHistory.spec.ts deleted file mode 100644 index c47a4d19b..000000000 --- a/browser_tests/tests/chatHistory.spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -import type { Page } from '@playwright/test' -import { expect } from '@playwright/test' - -import { comfyPageFixture as test } from '../fixtures/ComfyPage' - -test.beforeEach(async ({ comfyPage }) => { - await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled') -}) - -interface ChatHistoryEntry { - prompt: string - response: string - response_id: string -} - -async function renderChatHistory(page: Page, history: ChatHistoryEntry[]) { - const nodeId = await page.evaluate(() => window['app'].graph.nodes[0]?.id) - // Simulate API sending display_component message - await page.evaluate( - ({ nodeId, history }) => { - const event = new CustomEvent('display_component', { - detail: { - node_id: nodeId, - component: 'ChatHistoryWidget', - props: { - history: JSON.stringify(history) - } - } - }) - window['app'].api.dispatchEvent(event) - return true - }, - { nodeId, history } - ) - - return nodeId -} - -test.describe('Chat History Widget', () => { - let nodeId: string - - test.beforeEach(async ({ comfyPage }) => { - nodeId = await renderChatHistory(comfyPage.page, [ - { prompt: 'Hello', response: 'World', response_id: '123' } - ]) - // Wait for chat history to be rendered - await comfyPage.page.waitForSelector('.pi-pencil') - }) - - test('displays chat history when receiving display_component message', async ({ - comfyPage - }) => { - // Verify the chat history is displayed correctly - await expect(comfyPage.page.getByText('Hello')).toBeVisible() - await expect(comfyPage.page.getByText('World')).toBeVisible() - }) - - test('handles message editing interaction', async ({ comfyPage }) => { - // Get first node's ID - nodeId = await comfyPage.page.evaluate(() => { - const node = window['app'].graph.nodes[0] - - // Make sure the node has a prompt widget (for editing functionality) - if (!node.widgets) { - node.widgets = [] - } - - // Add a prompt widget if it doesn't exist - if (!node.widgets.find((w) => w.name === 'prompt')) { - node.widgets.push({ - name: 'prompt', - type: 'text', - value: 'Original prompt' - }) - } - - return node.id - }) - - await renderChatHistory(comfyPage.page, [ - { - prompt: 'Message 1', - response: 'Response 1', - response_id: '123' - }, - { - prompt: 'Message 2', - response: 'Response 2', - response_id: '456' - } - ]) - await comfyPage.page.waitForSelector('.pi-pencil') - - const originalTextAreaInput = await comfyPage.page - .getByPlaceholder('text') - .nth(1) - .inputValue() - - // Click edit button on first message - await comfyPage.page.getByLabel('Edit').first().click() - await comfyPage.nextFrame() - - // Verify cancel button appears - await expect(comfyPage.page.getByLabel('Cancel')).toBeVisible() - - // Click cancel edit - await comfyPage.page.getByLabel('Cancel').click() - - // Verify prompt input is restored - await expect(comfyPage.page.getByPlaceholder('text').nth(1)).toHaveValue( - originalTextAreaInput - ) - }) - - test('handles real-time updates to chat history', async ({ comfyPage }) => { - // Send initial history - await renderChatHistory(comfyPage.page, [ - { - prompt: 'Initial message', - response: 'Initial response', - response_id: '123' - } - ]) - await comfyPage.page.waitForSelector('.pi-pencil') - - // Update history with additional messages - await renderChatHistory(comfyPage.page, [ - { - prompt: 'Follow-up', - response: 'New response', - response_id: '456' - } - ]) - await comfyPage.page.waitForSelector('.pi-pencil') - - // Move mouse over the canvas to force update - await comfyPage.page.mouse.move(100, 100) - await comfyPage.nextFrame() - - // Verify new messages appear - await expect(comfyPage.page.getByText('Follow-up')).toBeVisible() - await expect(comfyPage.page.getByText('New response')).toBeVisible() - }) -}) diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png index 58fde830b..0ba972ad6 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png index bdadd1749..57da49787 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index e0f553b5a..06820e19d 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png index 4ee9b6d31..67d61793b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png index cf611aecb..e09187f17 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png index 7868917a1..f7370a918 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png index 828839746..343acd94c 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png index cc8226891..3da563b50 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png index b3478f939..c117b280b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png index 81d734bf2..8ca85b6e7 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png index 00ae1aaaa..5111ad8b3 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png index b2357e6eb..9c21ec650 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png index 7f888c20a..f32a4488f 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png index 119ab2341..7fc93479d 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png index 2de7f2ff7..bc3836a39 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png index bcf07e076..7d53397ff 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png index f991c9373..ccd7dca52 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png index b777445a5..34bf9f8ce 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png index c730e2b1d..cb4009972 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png index da5c7a0b0..5e36a6120 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png index e98f64819..030c2c2c3 100644 Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/eslint.config.ts b/eslint.config.ts index a2dd753cc..46a3d94d2 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -140,8 +140,8 @@ export default defineConfig([ 'unused-imports/no-unused-imports': 'error', 'no-console': ['error', { allow: ['warn', 'error'] }], 'vue/no-v-html': 'off', - // Enforce dark-theme: instead of dark: prefix - 'vue/no-restricted-class': ['error', '/^dark:/'], + // Prohibit dark-theme: and dark: prefixes + 'vue/no-restricted-class': ['error', '/^dark(-theme)?:/'], 'vue/multi-word-component-names': 'off', // TODO: fix 'vue/no-template-shadow': 'off', // TODO: fix 'vue/match-component-import-name': 'error', diff --git a/packages/design-system/src/css/style.css b/packages/design-system/src/css/style.css index ead8ff59f..4c5c63bb3 100644 --- a/packages/design-system/src/css/style.css +++ b/packages/design-system/src/css/style.css @@ -160,7 +160,6 @@ --subscription-button-gradient: linear-gradient(315deg, rgb(105 230 255 / 0.15) 0%, rgb(99 73 233 / 0.50) 100%), radial-gradient(70.71% 70.71% at 50% 50%, rgb(62 99 222 / 0.15) 0.01%, rgb(66 0 123 / 0.50) 100%), linear-gradient(92deg, #D000FF 0.38%, #B009FE 37.07%, #3E1FFC 65.17%, #009DFF 103.86%), var(--color-button-surface, #2D2E32); - --modal-card-button-surface: var(--color-smoke-300); /* Code styling colors for help menu*/ --code-text-color: rgb(0 122 255 / 1); @@ -265,6 +264,13 @@ --palette-interface-panel-selected-surface: color-mix(in srgb, var(--interface-panel-surface) 87.5%, var(--contrast-mix-color)); --palette-interface-button-hover-surface: color-mix(in srgb, var(--interface-panel-surface) 82%, var(--contrast-mix-color)); + --modal-card-background: var(--secondary-background); + --modal-card-button-surface: var(--color-smoke-300); + --modal-card-placeholder-background: var(--color-smoke-600); + --modal-card-tag-background: var(--color-smoke-200); + --modal-card-tag-foreground: var(--base-foreground); + --modal-panel-background: var(--color-white); + } .dark-theme { @@ -286,8 +292,6 @@ --subscription-button-gradient: linear-gradient(315deg, rgb(105 230 255 / 0.15) 0%, rgb(99 73 233 / 0.50) 100%), radial-gradient(70.71% 70.71% at 50% 50%, rgb(62 99 222 / 0.15) 0.01%, rgb(66 0 123 / 0.50) 100%), linear-gradient(92deg, #D000FF 0.38%, #B009FE 37.07%, #3E1FFC 65.17%, #009DFF 103.86%), var(--color-button-surface, #2D2E32); - --modal-card-button-surface: var(--color-charcoal-300); - --dialog-surface: var(--color-neutral-700); --interface-menu-component-surface-hovered: var(--color-charcoal-400); @@ -362,6 +366,14 @@ --component-node-widget-background-selected: var(--color-charcoal-100); --component-node-widget-background-disabled: var(--color-alpha-charcoal-600-30); --component-node-widget-background-highlighted: var(--color-graphite-400); + + --modal-card-background: var(--secondary-background); + --modal-card-button-surface: var(--color-charcoal-300); + --modal-card-placeholder-background: var(--secondary-background); + --modal-card-tag-background: var(--color-charcoal-200); + --modal-card-tag-foreground: var(--base-foreground); + --modal-panel-background: var(--color-charcoal-600); + } @theme inline { @@ -372,7 +384,14 @@ --color-button-surface: var(--button-surface); --color-button-surface-contrast: var(--button-surface-contrast); --color-subscription-button-gradient: var(--subscription-button-gradient); + + --color-modal-card-background: var(--modal-card-background); --color-modal-card-button-surface: var(--modal-card-button-surface); + --color-modal-card-placeholder-background: var(--modal-card-placeholder-background); + --color-modal-card-tag-background: var(--modal-card-tag-background); + --color-modal-card-tag-foreground: var(--modal-card-tag-foreground); + --color-modal-panel-background: var(--modal-panel-background); + --color-dialog-surface: var(--dialog-surface); --color-interface-menu-component-surface-hovered: var( --interface-menu-component-surface-hovered diff --git a/src/components/CLAUDE.md b/src/components/CLAUDE.md index 432fe7144..1cb50da67 100644 --- a/src/components/CLAUDE.md +++ b/src/components/CLAUDE.md @@ -30,7 +30,7 @@ ## Styling - Use Tailwind CSS only (no custom CSS) -- Dark theme: use "dark-theme:" prefix +- Use the correct tokens from style.css in the design system package - For common operations, try to use existing VueUse composables that automatically handle effect scope - Example: Use `useElementHover` instead of manually managing mouseover/mouseout event listeners - Example: Use `useIntersectionObserver` for visibility detection instead of custom scroll handlers diff --git a/src/components/bottomPanel/tabs/shortcuts/ShortcutsList.vue b/src/components/bottomPanel/tabs/shortcuts/ShortcutsList.vue index 1481b012f..f4a9a4c35 100644 --- a/src/components/bottomPanel/tabs/shortcuts/ShortcutsList.vue +++ b/src/components/bottomPanel/tabs/shortcuts/ShortcutsList.vue @@ -7,7 +7,7 @@ class="flex flex-col" >

{{ getSubcategoryTitle(subcategory) }}

@@ -16,7 +16,7 @@
@@ -32,7 +32,7 @@ {{ formatKey(key) }} @@ -100,21 +100,3 @@ const formatKey = (key: string): string => { return keyMap[key] || key } - - diff --git a/src/components/button/IconGroup.vue b/src/components/button/IconGroup.vue index 9e73edfab..bec0ac7fb 100644 --- a/src/components/button/IconGroup.vue +++ b/src/components/button/IconGroup.vue @@ -10,8 +10,7 @@ import { cn } from '@/utils/tailwindUtil' const iconGroupClasses = cn( 'flex justify-center items-center shrink-0', 'outline-hidden border-none p-0 rounded-lg', - 'bg-white dark-theme:bg-zinc-700', - 'text-neutral-950 dark-theme:text-white', + 'bg-secondary-background shadow-sm', 'transition-all duration-200', 'cursor-pointer' ) diff --git a/src/components/button/MoreButton.vue b/src/components/button/MoreButton.vue index 2cdf394fe..ea0f98708 100644 --- a/src/components/button/MoreButton.vue +++ b/src/components/button/MoreButton.vue @@ -7,13 +7,24 @@ @@ -26,7 +37,7 @@ diff --git a/src/components/card/CardContainer.vue b/src/components/card/CardContainer.vue index 7b0822aec..8b8f81944 100644 --- a/src/components/card/CardContainer.vue +++ b/src/components/card/CardContainer.vue @@ -47,8 +47,8 @@ const containerClasses = computed(() => { // Variant styles const variantClasses = { default: cn( - hasBackground && 'bg-white dark-theme:bg-zinc-800', - hasBorder && 'border border-zinc-200 dark-theme:border-zinc-700', + hasBackground && 'bg-modal-card-background', + hasBorder && 'border border-border-default', hasShadow && 'shadow-sm', hasCursor && 'cursor-pointer' ), @@ -57,9 +57,9 @@ const containerClasses = computed(() => { 'p-2 transition-colors duration-200' ), outline: cn( - hasBorder && 'border-2 border-zinc-300 dark-theme:border-zinc-600', + hasBorder && 'border-2 border-border-subtle', hasCursor && 'cursor-pointer', - 'hover:border-zinc-400 dark-theme:hover:border-zinc-500 transition-colors' + 'hover:border-border-subtle/50 transition-colors' ) } diff --git a/src/components/card/CardDescription.vue b/src/components/card/CardDescription.vue index 0b5e2de5c..e871594b5 100644 --- a/src/components/card/CardDescription.vue +++ b/src/components/card/CardDescription.vue @@ -1,7 +1,5 @@ - - diff --git a/src/components/chip/SquareChip.vue b/src/components/chip/SquareChip.vue index 6a47bb643..08f2fff38 100644 --- a/src/components/chip/SquareChip.vue +++ b/src/components/chip/SquareChip.vue @@ -19,7 +19,7 @@ const baseClasses = const variantStyles = { dark: 'bg-zinc-500/40 text-white/90', - light: 'backdrop-blur-[2px] bg-white/50 text-zinc-900 dark-theme:text-white' + light: cn('backdrop-blur-[2px] bg-base-background/50 text-base-foreground') } const chipClasses = computed(() => { diff --git a/src/components/common/FormImageUpload.vue b/src/components/common/FormImageUpload.vue index 661c5ceb8..c2eb98b1f 100644 --- a/src/components/common/FormImageUpload.vue +++ b/src/components/common/FormImageUpload.vue @@ -3,7 +3,7 @@
-
+
{{ $t('templateWorkflows.resultsCount', { count: filteredCount, diff --git a/src/components/dialog/content/MissingCoreNodesMessage.vue b/src/components/dialog/content/MissingCoreNodesMessage.vue index ea0dbc942..0274191af 100644 --- a/src/components/dialog/content/MissingCoreNodesMessage.vue +++ b/src/components/dialog/content/MissingCoreNodesMessage.vue @@ -24,16 +24,14 @@ :key="version" class="ml-4" > -
+
{{ $t('loadWorkflowWarning.coreNodesFromVersion', { version: version || 'unknown' }) }}
-
+
{{ getUniqueNodeNames(nodes).join(', ') }}
diff --git a/src/components/graph/selectionToolbox/BypassButton.test.ts b/src/components/graph/selectionToolbox/BypassButton.test.ts index 1f8a5d34f..9fdcd971f 100644 --- a/src/components/graph/selectionToolbox/BypassButton.test.ts +++ b/src/components/graph/selectionToolbox/BypassButton.test.ts @@ -84,18 +84,6 @@ describe('BypassButton', () => { ) }) - it('should show normal styling when node is not bypassed', () => { - const normalNode = { ...mockLGraphNode, mode: LGraphEventMode.ALWAYS } - canvasStore.selectedItems = [normalNode] as any - - const wrapper = mountComponent() - const button = wrapper.find('button') - - expect(button.classes()).not.toContain( - 'dark-theme:[&:not(:active)]:!bg-charcoal-600' - ) - }) - it('should show bypassed styling when node is bypassed', async () => { const bypassedNode = { ...mockLGraphNode, mode: LGraphEventMode.BYPASS } canvasStore.selectedItems = [bypassedNode] as any diff --git a/src/components/graph/selectionToolbox/ExecuteButton.vue b/src/components/graph/selectionToolbox/ExecuteButton.vue index 8e5666b34..f54a3e84f 100644 --- a/src/components/graph/selectionToolbox/ExecuteButton.vue +++ b/src/components/graph/selectionToolbox/ExecuteButton.vue @@ -4,13 +4,13 @@ value: t('selectionToolbox.executeButton.tooltip'), showDelay: 1000 }" - class="size-8 bg-azure-400 !p-0 dark-theme:bg-azure-600" + class="size-8 bg-primary-background text-white p-0" text @mouseenter="() => handleMouseEnter()" @mouseleave="() => handleMouseLeave()" @click="handleClick" > - + diff --git a/src/components/graph/selectionToolbox/MenuOptionItem.vue b/src/components/graph/selectionToolbox/MenuOptionItem.vue index fb1f8c01a..ead8fb183 100644 --- a/src/components/graph/selectionToolbox/MenuOptionItem.vue +++ b/src/components/graph/selectionToolbox/MenuOptionItem.vue @@ -1,8 +1,5 @@ diff --git a/src/components/graph/selectionToolbox/VerticalDivider.vue b/src/components/graph/selectionToolbox/VerticalDivider.vue index ac6768a2c..ac9cce428 100644 --- a/src/components/graph/selectionToolbox/VerticalDivider.vue +++ b/src/components/graph/selectionToolbox/VerticalDivider.vue @@ -1,5 +1,3 @@ diff --git a/src/components/graph/widgets/ChatHistoryWidget.test.ts b/src/components/graph/widgets/ChatHistoryWidget.test.ts deleted file mode 100644 index e90e0c853..000000000 --- a/src/components/graph/widgets/ChatHistoryWidget.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { mount } from '@vue/test-utils' -import { describe, expect, it, vi } from 'vitest' -import { createI18n } from 'vue-i18n' - -import ChatHistoryWidget from '@/components/graph/widgets/ChatHistoryWidget.vue' - -const i18n = createI18n({ - legacy: false, - locale: 'en', - messages: { - en: { - g: { edit: 'Edit' }, - chatHistory: { - cancelEdit: 'Cancel edit', - cancelEditTooltip: 'Cancel edit' - } - } - } -}) - -vi.mock('@/components/graph/widgets/chatHistory/CopyButton.vue', () => ({ - default: { - name: 'CopyButton', - template: '
', - props: ['text'] - } -})) - -vi.mock('@/components/graph/widgets/chatHistory/ResponseBlurb.vue', () => ({ - default: { - name: 'ResponseBlurb', - template: '
', - props: ['text'] - } -})) - -describe('ChatHistoryWidget.vue', () => { - const mockHistory = JSON.stringify([ - { prompt: 'Test prompt', response: 'Test response', response_id: '123' } - ]) - - const mountWidget = (props: { history: string; widget?: any }) => { - return mount(ChatHistoryWidget, { - props, - global: { - plugins: [i18n], - stubs: { - Button: { - template: '', - props: ['icon', 'aria-label'] - }, - ScrollPanel: { template: '
' } - } - } - }) - } - - it('renders chat history correctly', () => { - const wrapper = mountWidget({ history: mockHistory }) - expect(wrapper.text()).toContain('Test prompt') - expect(wrapper.text()).toContain('Test response') - }) - - it('handles empty history', () => { - const wrapper = mountWidget({ history: '[]' }) - expect(wrapper.find('.mb-4').exists()).toBe(false) - }) - - it('edits previous prompts', () => { - const mockWidget = { - node: { widgets: [{ name: 'prompt', value: '' }] } - } - - const wrapper = mountWidget({ history: mockHistory, widget: mockWidget }) - const vm = wrapper.vm as any - vm.handleEdit(0) - - expect(mockWidget.node.widgets[0].value).toContain('Test prompt') - expect(mockWidget.node.widgets[0].value).toContain('starting_point_id') - }) - - it('cancels editing correctly', () => { - const mockWidget = { - node: { widgets: [{ name: 'prompt', value: 'Original value' }] } - } - - const wrapper = mountWidget({ history: mockHistory, widget: mockWidget }) - const vm = wrapper.vm as any - - vm.handleEdit(0) - vm.handleCancelEdit() - - expect(mockWidget.node.widgets[0].value).toBe('Original value') - }) -}) diff --git a/src/components/graph/widgets/ChatHistoryWidget.vue b/src/components/graph/widgets/ChatHistoryWidget.vue deleted file mode 100644 index 37bb8a266..000000000 --- a/src/components/graph/widgets/ChatHistoryWidget.vue +++ /dev/null @@ -1,134 +0,0 @@ - - - diff --git a/src/components/graph/widgets/chatHistory/CopyButton.vue b/src/components/graph/widgets/chatHistory/CopyButton.vue deleted file mode 100644 index 4a44d05ce..000000000 --- a/src/components/graph/widgets/chatHistory/CopyButton.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/src/components/graph/widgets/chatHistory/ResponseBlurb.vue b/src/components/graph/widgets/chatHistory/ResponseBlurb.vue deleted file mode 100644 index e2326c431..000000000 --- a/src/components/graph/widgets/chatHistory/ResponseBlurb.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/src/components/input/MultiSelect.accessibility.stories.ts b/src/components/input/MultiSelect.accessibility.stories.ts deleted file mode 100644 index 3decd2a21..000000000 --- a/src/components/input/MultiSelect.accessibility.stories.ts +++ /dev/null @@ -1,380 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/vue3-vite' -import type { MultiSelectProps } from 'primevue/multiselect' -import { ref } from 'vue' - -import MultiSelect from './MultiSelect.vue' -import type { SelectOption } from './types' - -// Combine our component props with PrimeVue MultiSelect props -interface ExtendedProps extends Partial { - // Our custom props - label?: string - showSearchBox?: boolean - showSelectedCount?: boolean - showClearButton?: boolean - searchPlaceholder?: string - listMaxHeight?: string - popoverMinWidth?: string - popoverMaxWidth?: string - // Override modelValue type to match our Option type - modelValue?: SelectOption[] -} - -const meta: Meta = { - title: 'Components/Input/MultiSelect/Accessibility', - component: MultiSelect, - tags: ['autodocs'], - parameters: { - docs: { - description: { - component: ` -# MultiSelect Accessibility Guide - -This MultiSelect component provides full keyboard accessibility and screen reader support following WCAG 2.1 AA guidelines. - -## Keyboard Navigation - -- **Tab** - Focus the trigger button -- **Enter/Space** - Open/close dropdown when focused -- **Arrow Up/Down** - Navigate through options when dropdown is open -- **Enter/Space** - Select/deselect options when navigating -- **Escape** - Close dropdown - -## Screen Reader Support - -- Uses \`role="combobox"\` to identify as dropdown -- \`aria-haspopup="listbox"\` indicates popup contains list -- \`aria-expanded\` shows dropdown state -- \`aria-label\` provides accessible name with i18n fallback -- Selected count announced to screen readers - -## Testing Instructions - -1. **Tab Navigation**: Use Tab key to focus the component -2. **Keyboard Opening**: Press Enter or Space to open dropdown -3. **Option Navigation**: Use Arrow keys to navigate options -4. **Selection**: Press Enter/Space to select options -5. **Closing**: Press Escape to close dropdown -6. **Screen Reader**: Test with screen reader software - -Try these stories with keyboard-only navigation! - ` - } - } - }, - argTypes: { - label: { - control: 'text', - description: 'Label for the trigger button' - }, - showSearchBox: { - control: 'boolean', - description: 'Show search box in dropdown header' - }, - showSelectedCount: { - control: 'boolean', - description: 'Show selected count in dropdown header' - }, - showClearButton: { - control: 'boolean', - description: 'Show clear all button in dropdown header' - } - } -} - -export default meta -type Story = StoryObj - -const frameworkOptions = [ - { name: 'React', value: 'react' }, - { name: 'Vue', value: 'vue' }, - { name: 'Angular', value: 'angular' }, - { name: 'Svelte', value: 'svelte' }, - { name: 'TypeScript', value: 'typescript' }, - { name: 'JavaScript', value: 'javascript' } -] - -export const KeyboardNavigationDemo: Story = { - render: (args) => ({ - components: { MultiSelect }, - setup() { - const selectedFrameworks = ref([]) - const searchQuery = ref('') - - return { - args: { - ...args, - options: frameworkOptions, - modelValue: selectedFrameworks, - 'onUpdate:modelValue': (value: SelectOption[]) => { - selectedFrameworks.value = value - }, - 'onUpdate:searchQuery': (value: string) => { - searchQuery.value = value - } - }, - selectedFrameworks, - searchQuery - } - }, - template: ` -
-
-

🎯 Keyboard Navigation Test

-

- Use your keyboard to navigate this MultiSelect: -

-
    -
  1. Tab to focus the dropdown
  2. -
  3. Enter/Space to open dropdown
  4. -
  5. Arrow Up/Down to navigate options
  6. -
  7. Enter/Space to select options
  8. -
  9. Escape to close dropdown
  10. -
-
- -
- - -

- Selected: {{ selectedFrameworks.map(f => f.name).join(', ') || 'None' }} -

-
-
- ` - }), - args: { - label: 'Choose Frameworks', - showSearchBox: true, - showSelectedCount: true, - showClearButton: true - } -} - -export const ScreenReaderFriendly: Story = { - render: (args) => ({ - components: { MultiSelect }, - setup() { - const selectedColors = ref([]) - const selectedSizes = ref([]) - - const colorOptions = [ - { name: 'Red', value: 'red' }, - { name: 'Blue', value: 'blue' }, - { name: 'Green', value: 'green' }, - { name: 'Yellow', value: 'yellow' } - ] - - const sizeOptions = [ - { name: 'Small', value: 'sm' }, - { name: 'Medium', value: 'md' }, - { name: 'Large', value: 'lg' }, - { name: 'Extra Large', value: 'xl' } - ] - - return { - selectedColors, - selectedSizes, - colorOptions, - sizeOptions, - args - } - }, - template: ` -
-
-

♿ Screen Reader Test

-

- These dropdowns have proper ARIA attributes and labels for screen readers: -

-
    -
  • role="combobox" identifies as dropdown
  • -
  • aria-haspopup="listbox" indicates popup type
  • -
  • aria-expanded shows open/closed state
  • -
  • aria-label provides accessible name
  • -
  • Selection count announced to assistive technology
  • -
-
- -
-
- - -

- {{ selectedColors.length }} color(s) selected -

-
- -
- - -

- {{ selectedSizes.length }} size(s) selected -

-
-
-
- ` - }) -} - -export const FocusManagement: Story = { - render: (args) => ({ - components: { MultiSelect }, - setup() { - const selectedItems = ref([]) - const focusTestOptions = [ - { name: 'Option A', value: 'a' }, - { name: 'Option B', value: 'b' }, - { name: 'Option C', value: 'c' } - ] - - return { - selectedItems, - focusTestOptions, - args - } - }, - template: ` -
-
-

🎯 Focus Management Test

-

- Test focus behavior with multiple form elements: -

-
- -
-
- - -
- -
- - -
- -
- - -
- - -
- -
- Test: Tab through all elements and verify focus rings are visible and logical. -
-
- ` - }) -} - -export const AccessibilityChecklist: Story = { - render: () => ({ - template: ` -
-
-

♿ MultiSelect Accessibility Checklist

- -
-
-

✅ Implemented Features

-
    -
  • - - Keyboard Navigation: Tab, Enter, Space, Arrow keys, Escape -
  • -
  • - - ARIA Attributes: role, aria-haspopup, aria-expanded, aria-label -
  • -
  • - - Focus Management: Visible focus rings and logical tab order -
  • -
  • - - Internationalization: Translatable aria-label fallbacks -
  • -
  • - - Screen Reader Support: Proper announcements and state -
  • -
  • - - Color Contrast: Meets WCAG AA requirements -
  • -
-
- -
-

📋 Testing Guidelines

-
    -
  1. Keyboard Only: Navigate using only keyboard
  2. -
  3. Screen Reader: Test with NVDA, JAWS, or VoiceOver
  4. -
  5. Focus Visible: Ensure focus rings are always visible
  6. -
  7. Tab Order: Verify logical progression
  8. -
  9. Announcements: Check state changes are announced
  10. -
  11. Escape Behavior: Escape always closes dropdown
  12. -
-
-
- -
-

🎯 Quick Test

-

- Close your eyes, use only the keyboard, and try to select multiple options from any dropdown above. - If you can successfully navigate and make selections, the accessibility implementation is working! -

-
-
-
- ` - }) -} diff --git a/src/components/input/MultiSelect.stories.ts b/src/components/input/MultiSelect.stories.ts index e6b5d9144..d66a70653 100644 --- a/src/components/input/MultiSelect.stories.ts +++ b/src/components/input/MultiSelect.stories.ts @@ -102,7 +102,7 @@ export const Default: Story = { :showClearButton="args.showClearButton" :searchPlaceholder="args.searchPlaceholder" /> -
+

Selected: {{ selected.length > 0 ? selected.map(s => s.name).join(', ') : 'None' }}

@@ -135,7 +135,7 @@ export const WithPreselectedValues: Story = { :showClearButton="args.showClearButton" :searchPlaceholder="args.searchPlaceholder" /> -
+

Selected: {{ selected.map(s => s.name).join(', ') }}

@@ -229,7 +229,7 @@ export const MultipleSelectors: Story = { />
-
+

Current Selection:

Frameworks: {{ selectedFrameworks.length > 0 ? selectedFrameworks.map(s => s.name).join(', ') : 'None' }}

diff --git a/src/components/input/MultiSelect.vue b/src/components/input/MultiSelect.vue index 851439a3a..74a7035b1 100644 --- a/src/components/input/MultiSelect.vue +++ b/src/components/input/MultiSelect.vue @@ -13,12 +13,77 @@ option-label="name" unstyled :max-selected-labels="0" - :pt="pt" + :pt="{ + root: ({ props }: MultiSelectPassThroughMethodOptions) => ({ + class: cn( + 'h-10 relative inline-flex cursor-pointer select-none', + 'rounded-lg bg-base-background text-base-foreground', + 'transition-all duration-200 ease-in-out', + 'border-[2.5px] border-solid', + selectedCount > 0 + ? 'border-node-component-border' + : 'border-transparent', + 'focus-within:border-node-component-border', + { 'opacity-60 cursor-default': props.disabled } + ) + }), + labelContainer: { + class: + 'flex-1 flex items-center overflow-hidden whitespace-nowrap pl-4 py-2 ' + }, + label: { + class: 'p-0' + }, + dropdown: { + class: 'flex shrink-0 cursor-pointer items-center justify-center px-3' + }, + header: () => ({ + class: + showSearchBox || showSelectedCount || showClearButton + ? 'block' + : 'hidden' + }), + // Overlay & list visuals unchanged + overlay: { + class: cn( + 'mt-2 rounded-lg py-2 px-2', + 'bg-base-background', + 'text-base-foreground', + 'border border-solid border-border-default' + ) + }, + listContainer: () => ({ + style: { maxHeight: `min(${listMaxHeight}, 50vh)` }, + class: 'scrollbar-custom' + }), + list: { + class: 'flex flex-col gap-0 p-0 m-0 list-none border-none text-sm' + }, + // Option row hover and focus tone + option: ({ context }: MultiSelectPassThroughMethodOptions) => ({ + class: cn( + 'flex gap-2 items-center h-10 px-2 rounded-lg', + 'hover:bg-secondary-background-hover', + // Add focus/highlight state for keyboard navigation + context?.focused && + 'bg-secondary-background-selected hover:bg-secondary-background-selected' + ) + }), + // Hide built-in checkboxes entirely via PT (no :deep) + pcHeaderCheckbox: { + root: { class: 'hidden' }, + style: { display: 'none' } + }, + pcOptionCheckbox: { + root: { class: 'hidden' }, + style: { display: 'none' } + } + }" :aria-label="label || t('g.multiSelectDropdown')" role="combobox" :aria-expanded="false" aria-haspopup="listbox" - :tabindex="0" + tabindex="0" >