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" >
- - Use your keyboard to navigate this MultiSelect: -
-- Selected: {{ selectedFrameworks.map(f => f.name).join(', ') || 'None' }} -
-- These dropdowns have proper ARIA attributes and labels for screen readers: -
-role="combobox" identifies as dropdownaria-haspopup="listbox" indicates popup typearia-expanded shows open/closed statearia-label provides accessible name- {{ selectedColors.length }} color(s) selected -
-- {{ selectedSizes.length }} size(s) selected -
-- Test focus behavior with multiple form elements: -
-- 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! -
-Selected: {{ selected.length > 0 ? selected.map(s => s.name).join(', ') : 'None' }}
Selected: {{ selected.map(s => s.name).join(', ') }}
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" > {{ selectedCount > 0 @@ -52,22 +117,22 @@ :label="$t('g.clearAll')" type="transparent" size="fit-content" - class="text-sm text-blue-500 dark-theme:text-blue-600" + class="text-sm text-text-primary" @click.stop="selectedItems = []" />- Use your keyboard to navigate these SingleSelect dropdowns: -
-- Selected: {{ selectedSort ? sortOptions.find(o => o.value === selectedSort)?.name : 'None' }} -
-- Selected: {{ selectedPriority ? priorityOptions.find(o => o.value === selectedPriority)?.name : 'None' }} -
-- These dropdowns have proper ARIA attributes and labels for screen readers: -
-role="combobox" identifies as dropdownaria-haspopup="listbox" indicates popup typearia-expanded shows open/closed statearia-label provides accessible name- Current: {{ selectedLanguage ? languageOptions.find(o => o.value === selectedLanguage)?.name : 'None selected' }} -
-- Current: {{ selectedTheme ? themeOptions.find(o => o.value === selectedTheme)?.name : 'No theme selected' }} -
-- Test keyboard navigation through a complete form with SingleSelect components. - Tab order should be logical and all elements should be accessible. -
-{{ JSON.stringify(formData, null, 2) }}
- - Close your eyes, use only the keyboard, and try to select different options from any dropdown above. - If you can successfully navigate and make selections, the accessibility implementation is working! -
-- These accessibility features are built into the component with minimal performance impact. - The ARIA attributes and keyboard handlers add less than 1KB to the bundle size. -
-Selected: {{ selected ?? 'None' }}
Selected: {{ selected }}