Compare commits

...

9 Commits

Author SHA1 Message Date
Benjamin Lu
5f85198db7 Desktop maintenance: unsafe base path warning (#6750)
Surface unsafe base path validation in the desktop maintenance view and
add an installation-fix auto-refresh after successful tasks.

<img width="1080" height="870" alt="image"
src="https://github.com/user-attachments/assets/26fe61be-fed8-47c0-a921-604f0af018f8"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6750-Desktop-maintenance-unsafe-base-path-warning-2b06d73d36508147aeb4d19d02bbf0f0)
by [Unito](https://www.unito.io)
2025-11-20 02:13:52 +00:00
Comfy Org PR Bot
300c49231f 1.30.4 (#6738)
Patch version increment to 1.30.4

**Base branch:** `core/1.30`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6738-1-30-4-2af6d73d365081729041c4f88a8df082)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-18 12:25:14 -07:00
Comfy Org PR Bot
0184771b31 [backport core/1.30] feat: pre-fill user info in Zendesk support link (#6737)
Backport of #6586 to `core/1.30`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6737-backport-core-1-30-feat-pre-fill-user-info-in-Zendesk-support-link-2af6d73d36508194a374d834c37ee32b)
by [Unito](https://www.unito.io)

Co-authored-by: Marwan Ahmed <155799754+marawan206@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: bymyself <cbyrne@comfy.org>
2025-11-18 12:11:14 -07:00
Comfy Org PR Bot
30d4b8f600 [backport core/1.30] fix minimap navigation on touch devices (#6736)
Backport of #6580 to `core/1.30`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6736-backport-core-1-30-fix-minimap-navigation-on-touch-devices-2af6d73d3650813e80a5f5d3fa6b7269)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-11-18 12:10:36 -07:00
Christian Byrne
24144ebeea Backport PR #6589 (minimap and canvas bg color tokens) to core/1.30 (#6714)
## Summary
Backport of PR #6589 (commit 549ef79e02)
from main to core/1.30 branch.

Updates minimap and canvas background colors to use menu color tokens
(`--comfy-menu-bg`) for consistency across the interface.

## Changes
- Updated minimap container and panel backgrounds to use semantic color
tokens
- Updated canvas menu and zoom controls backgrounds to use consistent
tokens
- Updated user avatar, login button, and other interface elements
- Resolved 20 binary browser test snapshot conflicts using backportee's
copy
- Fixed 8 Vue component conflicts by accepting semantic token changes

## Resolved Conflicts
- **Binary snapshots (20 files)**: Used --theirs (backportee's copy) as
instructed
- **Source files (8 files)**: Accepted cherry-picked semantic token
changes
  - TopMenuSection.vue, UserAvatar.vue, CanvasModeSelector.vue
  - GraphCanvasMenu.vue, ZoomControlsModal.vue, LoginButton.vue  
  - MiniMap.vue, MiniMapPanel.vue

All linting and TypeScript checks passed.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6714-Backport-PR-6589-minimap-and-canvas-bg-color-tokens-to-core-1-30-2ad6d73d3650811d9c82d1e5f3a81a38)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-11-18 11:56:00 -07:00
Comfy Org PR Bot
fe9f423b9a [backport core/1.30] Fix partial execution inside subgraphs (#6704)
Backport of #6487 to `core/1.30`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6704-backport-core-1-30-Fix-partial-execution-inside-subgraphs-2ab6d73d365081d09667e6c44528ea74)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
2025-11-14 11:55:53 -07:00
Christian Byrne
bf24b6e729 [backport core/1.30]: UI color updates & tweaks (#6679)
## Summary
Backports PR #6381 (UI color updates & tweaks) to core/1.30 branch.

This includes:
- Text readability improvements
- Semantic color token consistency updates
- UI element styling refinements
- Component import path fixes (ComfyLogoTransparent → ComfyLogo)

## Merge Conflicts Resolved
- **ComfyMenuButton.vue**: Accepted cherry-picked version with import
changes
- **20 browser test snapshots**: Used backportee's copy as instructed

**Original commit**: fd236b3587

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6679-Backport-PR-6381-to-core-1-30-UI-color-updates-tweaks-2aa6d73d3650815689defd191e3dd86e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-14 11:40:10 -07:00
Christian Byrne
3be12e18e8 Backport: Css token standardization (#6363) (#6669)
## Summary
Backports the CSS token standardization changes from PR #6363 to the
`core/1.30` branch.

Resolved merge conflict in `MediaTitle.vue` and added missing
`truncateFilename` function to maintain compatibility.

**Original PR**: https://github.com/Comfy-Org/ComfyUI_frontend/pull/6363
**Cherry-picked commit**: bde5244a71

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6669-Backport-Css-token-standardization-6363-2aa6d73d36508153be09cdc67f6ac8a4)
by [Unito](https://www.unito.io)
2025-11-12 21:07:53 -07:00
Comfy Org PR Bot
85d3bc25d6 [backport core/1.30] Style: Token renaming and style organization (#6667)
Backport of #6337 to `core/1.30`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6667-backport-core-1-30-Style-Token-renaming-and-style-organization-2aa6d73d3650811ca3acfdb00dd52fed)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-11-12 17:56:21 -07:00
209 changed files with 1258 additions and 992 deletions

View File

@@ -16,7 +16,8 @@ export const DESKTOP_MAINTENANCE_TASKS: Readonly<MaintenanceTask>[] = [
execute: async () => await electron.setBasePath(),
name: 'Base path',
shortDescription: 'Change the application base path.',
errorDescription: 'Unable to open the base path. Please select a new one.',
errorDescription:
'The current base path is invalid or unsafe. Please select a new location.',
description:
'The base path is the default location where ComfyUI stores data. It is the location for the python environment, and may also contain models, custom nodes, and other extensions.',
isInstallationFix: true,

View File

@@ -85,6 +85,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
const electron = electronAPI()
// Reactive state
const lastUpdate = ref<InstallValidation | null>(null)
const isRefreshing = ref(false)
const isRunningTerminalCommand = computed(() =>
tasks.value
@@ -97,6 +98,13 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
.some((task) => getRunner(task)?.executing)
)
const unsafeBasePath = computed(
() => lastUpdate.value?.unsafeBasePath === true
)
const unsafeBasePathReason = computed(
() => lastUpdate.value?.unsafeBasePathReason
)
// Task list
const tasks = ref(DESKTOP_MAINTENANCE_TASKS)
@@ -123,6 +131,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
* @param validationUpdate Update details passed in by electron
*/
const processUpdate = (validationUpdate: InstallValidation) => {
lastUpdate.value = validationUpdate
const update = validationUpdate as IndexedUpdate
isRefreshing.value = true
@@ -155,7 +164,11 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
}
const execute = async (task: MaintenanceTask) => {
return getRunner(task).execute(task)
const success = await getRunner(task).execute(task)
if (success && task.isInstallationFix) {
await refreshDesktopTasks()
}
return success
}
return {
@@ -163,6 +176,8 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
isRefreshing,
isRunningTerminalCommand,
isRunningInstallationFix,
unsafeBasePath,
unsafeBasePathReason,
execute,
getRunner,
processUpdate,

View File

@@ -0,0 +1,159 @@
// eslint-disable-next-line storybook/no-renderer-packages
import type { Meta, StoryObj } from '@storybook/vue3'
import { defineAsyncComponent } from 'vue'
type UnsafeReason = 'appInstallDir' | 'updaterCache' | 'oneDrive' | null
type ValidationIssueState = 'OK' | 'warning' | 'error' | 'skipped'
type ValidationState = {
inProgress: boolean
installState: string
basePath?: ValidationIssueState
unsafeBasePath: boolean
unsafeBasePathReason: UnsafeReason
venvDirectory?: ValidationIssueState
pythonInterpreter?: ValidationIssueState
pythonPackages?: ValidationIssueState
uv?: ValidationIssueState
git?: ValidationIssueState
vcRedist?: ValidationIssueState
upgradePackages?: ValidationIssueState
}
const validationState: ValidationState = {
inProgress: false,
installState: 'installed',
basePath: 'OK',
unsafeBasePath: false,
unsafeBasePathReason: null,
venvDirectory: 'OK',
pythonInterpreter: 'OK',
pythonPackages: 'OK',
uv: 'OK',
git: 'OK',
vcRedist: 'OK',
upgradePackages: 'OK'
}
const createMockElectronAPI = () => {
const logListeners: Array<(message: string) => void> = []
const getValidationUpdate = () => ({
...validationState
})
return {
getPlatform: () => 'darwin',
changeTheme: (_theme: unknown) => {},
onLogMessage: (listener: (message: string) => void) => {
logListeners.push(listener)
},
showContextMenu: (_options: unknown) => {},
Events: {
trackEvent: (_eventName: string, _data?: unknown) => {}
},
Validation: {
onUpdate: (_callback: (update: unknown) => void) => {},
async getStatus() {
return getValidationUpdate()
},
async validateInstallation(callback: (update: unknown) => void) {
callback(getValidationUpdate())
},
async complete() {
// Only allow completion when the base path is safe
return !validationState.unsafeBasePath
},
dispose: () => {}
},
setBasePath: () => Promise.resolve(true),
reinstall: () => Promise.resolve(),
uv: {
installRequirements: () => Promise.resolve(),
clearCache: () => Promise.resolve(),
resetVenv: () => Promise.resolve()
}
}
}
const ensureElectronAPI = () => {
const globalWindow = window as unknown as { electronAPI?: unknown }
if (!globalWindow.electronAPI) {
globalWindow.electronAPI = createMockElectronAPI()
}
return globalWindow.electronAPI
}
const MaintenanceView = defineAsyncComponent(async () => {
ensureElectronAPI()
const module = await import('./MaintenanceView.vue')
return module.default
})
const meta: Meta<typeof MaintenanceView> = {
title: 'Desktop/Views/MaintenanceView',
component: MaintenanceView,
parameters: {
layout: 'fullscreen',
backgrounds: {
default: 'dark',
values: [
{ name: 'dark', value: '#0a0a0a' },
{ name: 'neutral-900', value: '#171717' },
{ name: 'neutral-950', value: '#0a0a0a' }
]
}
}
}
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
name: 'All tasks OK',
render: () => ({
components: { MaintenanceView },
setup() {
validationState.inProgress = false
validationState.installState = 'installed'
validationState.basePath = 'OK'
validationState.unsafeBasePath = false
validationState.unsafeBasePathReason = null
validationState.venvDirectory = 'OK'
validationState.pythonInterpreter = 'OK'
validationState.pythonPackages = 'OK'
validationState.uv = 'OK'
validationState.git = 'OK'
validationState.vcRedist = 'OK'
validationState.upgradePackages = 'OK'
ensureElectronAPI()
return {}
},
template: '<MaintenanceView />'
})
}
export const UnsafeBasePathOneDrive: Story = {
name: 'Unsafe base path (OneDrive)',
render: () => ({
components: { MaintenanceView },
setup() {
validationState.inProgress = false
validationState.installState = 'installed'
validationState.basePath = 'error'
validationState.unsafeBasePath = true
validationState.unsafeBasePathReason = 'oneDrive'
validationState.venvDirectory = 'OK'
validationState.pythonInterpreter = 'OK'
validationState.pythonPackages = 'OK'
validationState.uv = 'OK'
validationState.git = 'OK'
validationState.vcRedist = 'OK'
validationState.upgradePackages = 'OK'
ensureElectronAPI()
return {}
},
template: '<MaintenanceView />'
})
}

View File

@@ -47,6 +47,28 @@
</div>
</div>
<!-- Unsafe migration warning -->
<div v-if="taskStore.unsafeBasePath" class="my-4">
<p class="flex items-start gap-3 text-neutral-300">
<Tag
icon="pi pi-exclamation-triangle"
severity="warn"
:value="t('icon.exclamation-triangle')"
/>
<span>
<strong class="block mb-1">
{{ t('maintenance.unsafeMigration.title') }}
</strong>
<span class="block mb-1">
{{ unsafeReasonText }}
</span>
<span class="block text-sm text-neutral-400">
{{ t('maintenance.unsafeMigration.action') }}
</span>
</span>
</p>
</div>
<!-- Tasks -->
<TaskListPanel
class="border-neutral-700 border-solid border-x-0 border-y"
@@ -89,10 +111,10 @@
import { PrimeIcons } from '@primevue/core/api'
import Button from 'primevue/button'
import SelectButton from 'primevue/selectbutton'
import Tag from 'primevue/tag'
import Toast from 'primevue/toast'
import { useToast } from 'primevue/usetoast'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { watch } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import RefreshButton from '@/components/common/RefreshButton.vue'
import StatusTag from '@/components/maintenance/StatusTag.vue'
@@ -139,6 +161,27 @@ const filterOptions = ref([
/** Filter binding; can be set to show all tasks, or only errors. */
const filter = ref<MaintenanceFilter>(filterOptions.value[0])
const unsafeReasonText = computed(() => {
const reason = taskStore.unsafeBasePathReason
if (!reason) {
return t('maintenance.unsafeMigration.generic')
}
if (reason === 'appInstallDir') {
return t('maintenance.unsafeMigration.appInstallDir')
}
if (reason === 'updaterCache') {
return t('maintenance.unsafeMigration.updaterCache')
}
if (reason === 'oneDrive') {
return t('maintenance.unsafeMigration.oneDrive')
}
return t('maintenance.unsafeMigration.generic')
})
/** If valid, leave the validation window. */
const completeValidation = async () => {
const isValid = await electron.Validation.complete()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -5,7 +5,6 @@ import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescrip
import { importX } from 'eslint-plugin-import-x'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import storybook from 'eslint-plugin-storybook'
import tailwind from 'eslint-plugin-tailwindcss'
import unusedImports from 'eslint-plugin-unused-imports'
import pluginVue from 'eslint-plugin-vue'
import { defineConfig } from 'eslint/config'
@@ -34,11 +33,7 @@ const settings = {
],
noWarnOnMultipleProjects: true
})
],
tailwindcss: {
config: `${import.meta.dirname}/packages/design-system/src/css/style.css`,
functions: ['cn', 'clsx', 'tw']
}
]
} as const
const commonParserOptions = {
@@ -94,10 +89,6 @@ export default defineConfig([
pluginJs.configs.recommended,
tseslintConfigs.recommended,
// Difference in typecheck on CI vs Local
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Bad types in the plugin
tailwind.configs['flat/recommended'],
pluginVue.configs['flat/recommended'],
eslintPluginPrettierRecommended,
storybook.configs['flat/recommended'],
@@ -129,7 +120,6 @@ export default defineConfig([
'import-x/no-relative-packages': 'error',
'unused-imports/no-unused-imports': 'error',
'no-console': ['error', { allow: ['warn', 'error'] }],
'tailwindcss/no-custom-classname': 'off', // TODO: fix
'vue/no-v-html': 'off',
// Enforce dark-theme: instead of dark: prefix
'vue/no-restricted-class': ['error', '/^dark:/'],

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.30.3",
"version": "1.30.4",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -61,7 +61,6 @@
"@storybook/vue3-vite": "catalog:",
"@tailwindcss/vite": "catalog:",
"@trivago/prettier-plugin-sort-imports": "catalog:",
"@types/eslint-plugin-tailwindcss": "catalog:",
"@types/fs-extra": "catalog:",
"@types/jsdom": "catalog:",
"@types/node": "catalog:",
@@ -78,7 +77,6 @@
"eslint-plugin-import-x": "catalog:",
"eslint-plugin-prettier": "catalog:",
"eslint-plugin-storybook": "catalog:",
"eslint-plugin-tailwindcss": "catalog:",
"eslint-plugin-unused-imports": "catalog:",
"eslint-plugin-vue": "catalog:",
"fs-extra": "^11.2.0",

View File

@@ -9,29 +9,18 @@
@config '../../tailwind.config.ts';
@media (prefers-color-scheme: dark) {
:root {
--fg-color: #fff;
--bg-color: #202020;
--content-bg: #4e4e4e;
--content-fg: #fff;
--content-hover-bg: #222;
--content-hover-fg: #fff;
}
}
@theme {
--text-xxs: 0.625rem;
--text-xxs--line-height: calc(1 / 0.625);
/* Spacing */
--spacing-xs: 8px;
--text-xxxs: 0.5625rem;
--text-xxxs--line-height: calc(1 / 0.5625);
/* Font Families */
--font-inter: 'Inter', sans-serif;
/* Palette Colors */
--color-charcoal-100: #171718;
--color-charcoal-100: #55565e;
--color-charcoal-200: #494a50;
--color-charcoal-300: #3c3d42;
--color-charcoal-400: #313235;
@@ -42,43 +31,60 @@
--color-neutral-550: #636363;
--color-stone-100: #828282;
--color-stone-200: #444444;
--color-stone-300: #bbbbbb;
--color-ash-300: #bbbbbb;
--color-ash-500: #828282;
--color-ash-800: #444444;
--color-ivory-100: #fdfbfa;
--color-ivory-200: #faf9f5;
--color-ivory-300: #f0eee6;
--color-gray-100: #f3f3f3;
--color-gray-200: #e9e9e9;
--color-gray-300: #e1e1e1;
--color-gray-400: #d9d9d9;
--color-gray-500: #c5c5c5;
--color-gray-600: #b4b4b4;
--color-gray-700: #a0a0a0;
--color-gray-800: #8a8a8a;
--color-smoke-100: #f3f3f3;
--color-smoke-200: #e9e9e9;
--color-smoke-300: #e1e1e1;
--color-smoke-400: #d9d9d9;
--color-smoke-500: #c5c5c5;
--color-smoke-600: #b4b4b4;
--color-smoke-700: #a0a0a0;
--color-smoke-800: #8a8a8a;
--color-sand-100: #e1ded5;
--color-sand-200: #d6cfc2;
--color-sand-200: #fff7d5;
--color-sand-300: #888682;
--color-pure-black: #000000;
--color-pure-white: #ffffff;
--color-sand-400: #eed7ac;
--color-slate-100: #9c9eab;
--color-slate-200: #9fa2bd;
--color-slate-300: #5b5e7d;
--color-brand-yellow: #f0ff41;
--color-brand-blue: #172dd7;
--color-white: #ffffff;
--color-black: #000000;
--color-electric-400: #f0ff41;
--color-sapphire-700: #172dd7;
--color-brand-yellow: var(--color-electric-400);
--color-brand-blue: var(--color-sapphire-700);
--color-azure-300: #78bae9;
--color-azure-400: #31b9f4;
--color-azure-600: #0b8ce9;
--color-cobalt-800: #185a8b;
--color-jade-400: #47e469;
--color-jade-600: #00cd72;
--color-gold-400: #fcbf64;
--color-gold-500: #fdab34;
--color-gold-600: #fd9903;
--color-coral-500: #f75951;
--color-coral-600: #e04e48;
--color-coral-700: #b33a3a;
--color-magenta-300: #ceaac9;
--color-magenta-700: #6a246a;
--color-blue-100: #0b8ce9;
--color-blue-200: #31b9f4;
--color-success-100: #00cd72;
--color-success-200: #47e469;
--color-warning-100: #fd9903;
--color-warning-200: #fcbf64;
--color-danger-100: #c02323;
--color-danger-200: #d62952;
@@ -90,28 +96,31 @@
--color-error: #962a2a;
--color-comfy-menu-secondary: var(--comfy-menu-secondary-bg);
--text-xxxs: 0.5625rem;
--text-xxxs--line-height: calc(1 / 0.5625);
--color-blue-selection: rgb(from var(--color-blue-100) r g b / 0.3);
--color-node-hover-100: rgb(from var(--color-charcoal-100) r g b/ 0.15);
--color-node-hover-200: rgb(from var(--color-charcoal-100) r g b/ 0.1);
--color-modal-tag: rgb(from var(--color-gray-400) r g b/ 0.4);
--color-blue-selection: rgb(from var(--color-azure-600) r g b / 0.3);
--color-node-hover-100: rgb(from var(--color-charcoal-800) r g b/ 0.15);
--color-node-hover-200: rgb(from var(--color-charcoal-800) r g b/ 0.1);
--color-modal-tag: rgb(from var(--color-smoke-400) r g b/ 0.4);
--color-alpha-charcoal-600-30: color-mix(
in srgb,
var(--color-charcoal-600) 30%,
transparent
);
--color-alpha-stone-100-20: color-mix(
--color-alpha-ash-500-20: color-mix(
in srgb,
var(--color-stone-100) 20%,
var(--color-ash-500) 20%,
transparent
);
--color-alpha-gray-500-50: color-mix(
--color-alpha-smoke-500-50: color-mix(
in srgb,
var(--color-gray-500) 50%,
var(--color-smoke-500) 50%,
transparent
);
--color-alpha-smoke-500-20: #c5c5c533;
--color-alpha-smoke-400-40: #d9d9d966;
--color-alpha-azure-600-30: #0b8ce94d;
--color-alpha-magenta-700-60: #6a246a99;
--color-alpha-magenta-300-60: #ceaac999;
/* PrimeVue pulled colors */
--color-muted: var(--p-text-muted-color);
@@ -145,8 +154,8 @@
--content-hover-bg: #adadad;
--content-hover-fg: #000;
--button-surface: var(--color-pure-white);
--button-surface-contrast: var(--color-pure-black);
--button-surface: var(--color-white);
--button-surface-contrast: var(--color-black);
/* Code styling colors for help menu*/
--code-text-color: rgb(0 122 255 / 1);
@@ -157,31 +166,36 @@
--accent-primary: var(--color-charcoal-700);
--backdrop: var(--color-white);
--button-hover-surface: var(--color-gray-200);
--button-active-surface: var(--color-gray-400);
--button-icon: var(--color-gray-600);
--button-hover-surface: var(--color-smoke-200);
--button-active-surface: var(--color-smoke-400);
--button-icon: var(--color-smoke-600);
--dialog-surface: var(--color-neutral-200);
--interface-menu-component-surface-hovered: var(--color-gray-200);
--interface-menu-component-surface-selected: var(--color-gray-400);
--interface-menu-keybind-surface-default: var(--color-gray-500);
--interface-panel-surface: var(--color-pure-white);
--interface-stroke: var(--color-gray-300);
--nav-background: var(--color-pure-white);
--node-border: var(--color-gray-300);
--node-component-border: var(--color-gray-400);
--node-component-disabled: var(--color-alpha-stone-100-20);
--interface-menu-component-surface-hovered: var(--color-smoke-200);
--interface-menu-component-surface-selected: var(--color-smoke-400);
--interface-menu-keybind-surface-default: var(--color-smoke-500);
--interface-panel-surface: var(--color-white);
--interface-stroke: var(--color-smoke-300);
--nav-background: var(--color-white);
--node-border: var(--color-smoke-300);
--node-component-border: var(--color-smoke-400);
--node-component-disabled: var(--color-alpha-ash-500-20);
--node-component-executing: var(--color-blue-500);
--node-component-header: var(--fg-color);
--node-component-header-icon: var(--color-stone-200);
--node-component-header-icon: var(--color-ash-800);
--node-component-header-surface: var(--color-white);
--node-component-outline: var(--color-black);
--node-component-ring: rgb(from var(--color-gray-500) r g b / 50%);
--node-component-ring: rgb(from var(--color-smoke-500) r g b / 50%);
--node-component-slot-dot-outline-opacity-mult: 1;
--node-component-slot-dot-outline-opacity: 5%;
--node-component-slot-dot-outline: var(--color-black);
--node-component-slot-text: var(--color-stone-200);
--node-component-surface-highlight: var(--color-stone-100);
--node-component-surface-hovered: var(--color-gray-200);
--node-component-slot-text: var(--color-ash-800);
--node-component-surface-highlight: var(--color-ash-500);
--node-component-surface-hovered: var(--color-smoke-200);
--node-component-surface-selected: var(--color-charcoal-200);
--node-component-surface: var(--color-white);
--node-component-tooltip: var(--color-charcoal-700);
@@ -193,40 +207,86 @@
);
--node-component-widget-skeleton-surface: var(--color-zinc-300);
--node-divider: var(--color-sand-100);
--node-icon-disabled: var(--color-alpha-gray-500-50);
--node-stroke: var(--color-gray-400);
--node-icon-disabled: var(--color-alpha-smoke-500-50);
--node-stroke: var(--color-smoke-400);
--node-stroke-selected: var(--color-accent-primary);
--node-stroke-error: var(--color-error);
--node-stroke-executing: var(--color-blue-100);
--text-secondary: var(--color-stone-100);
--node-stroke-executing: var(--color-azure-600);
--text-secondary: var(--color-ash-500);
--text-primary: var(--color-charcoal-700);
--input-surface: rgb(0 0 0 / 0.15);
/* Semantic tokens - light mode */
--muted-foreground: var(--color-charcoal-200);
--base-foreground: var(--color-charcoal-800);
--brand-yellow: var(--color-electric-400);
--brand-blue: var(--color-sapphire-700);
--secondary-background: var(--color-smoke-200);
--secondary-background-hover: var(--color-smoke-400);
--secondary-background-selected: var(--color-smoke-600);
--base-background: var(--color-white);
--primary-background: var(--color-azure-400);
--primary-background-hover: var(--color-cobalt-800);
--destructive-background: var(--color-coral-500);
--destructive-background-hover: var(--color-coral-600);
--inverted-background-hover: var(--color-charcoal-600);
--warning-background: var(--color-gold-400);
--warning-background-hover: var(--color-gold-500);
--border-default: var(--color-smoke-600);
--border-subtle: var(--color-smoke-400);
--muted-background: var(--color-smoke-700);
--accent-background: var(--color-smoke-800);
/* Default UI element color palette variables */
--palette-contrast-mix-color: #fff;
--palette-interface-panel-surface: var(--comfy-menu-bg);
--palette-interface-stroke: color-mix(in srgb, var(--interface-panel-surface) 75.5%, var(--contrast-mix-color));
--palette-interface-panel-box-shadow: 1px 1px 8px 0 rgb(0 0 0 / 0.4);
--palette-interface-panel-drop-shadow: 1px 1px 4px rgb(0 0 0 / 0.4);
--palette-interface-panel-hover-surface: color-mix(in srgb, var(--interface-panel-surface) 92.5%, var(--contrast-mix-color));
--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));
}
.dark-theme {
--accent-primary: var(--color-pure-white);
--fg-color: #fff;
--bg-color: #202020;
--content-bg: #4e4e4e;
--content-fg: #fff;
--content-hover-bg: #222;
--content-hover-fg: #fff;
--accent-primary: var(--color-white);
--backdrop: var(--color-neutral-900);
--button-surface: var(--color-charcoal-600);
--button-surface-contrast: var(--color-pure-white);
--button-surface-contrast: var(--color-white);
--button-hover-surface: var(--color-charcoal-600);
--button-active-surface: var(--color-charcoal-600);
--button-icon: var(--color-gray-800);
--button-icon: var(--color-smoke-800);
--dialog-surface: var(--color-neutral-700);
--interface-menu-component-surface-hovered: var(--color-charcoal-400);
--interface-menu-component-surface-selected: var(--color-charcoal-300);
--interface-menu-keybind-surface-default: var(--color-charcoal-200);
--interface-panel-surface: var(--color-charcoal-100);
--interface-panel-surface: var(--color-charcoal-800);
--interface-stroke: var(--color-charcoal-400);
--nav-background: var(--color-charcoal-100);
--nav-background: var(--color-charcoal-800);
--node-border: var(--color-charcoal-500);
--node-component-border: var(--color-stone-200);
--node-component-border: var(--color-ash-800);
--node-component-border-error: var(--color-danger-100);
--node-component-border-executing: var(--color-blue-500);
--node-component-border-selected: var(--color-charcoal-200);
--node-component-header-icon: var(--color-slate-300);
--node-component-header-surface: var(--color-charcoal-800);
--node-component-outline: var(--color-white);
--node-component-ring: rgb(var(--color-gray-500) / 20%);
--node-component-ring: rgb(var(--color-smoke-500) / 20%);
--node-component-slot-dot-outline-opacity: 10%;
--node-component-slot-dot-outline: var(--color-white);
--node-component-slot-text: var(--color-slate-200);
@@ -240,14 +300,37 @@
--node-component-widget-skeleton-surface: var(--color-zinc-800);
--node-component-disabled: var(--color-alpha-charcoal-600-30);
--node-divider: var(--color-charcoal-500);
--node-icon-disabled: var(--color-alpha-stone-100-20);
--node-stroke: var(--color-stone-200);
--node-stroke-selected: var(--color-pure-white);
--node-icon-disabled: var(--color-alpha-ash-500-20);
--node-stroke: var(--color-ash-800);
--node-stroke-selected: var(--color-white);
--node-stroke-error: var(--color-error);
--node-stroke-executing: var(--color-blue-100);
--node-stroke-executing: var(--color-azure-600);
--text-secondary: var(--color-slate-100);
--text-primary: var(--color-pure-white);
--text-primary: var(--color-white);
--input-surface: rgb(130 130 130 / 0.1);
/* Semantic tokens - dark mode */
--muted-foreground: var(--color-smoke-800);
--base-foreground: var(--color-white);
--brand-yellow: var(--color-electric-400);
--brand-blue: var(--color-sapphire-700);
--secondary-background: var(--color-charcoal-600);
--secondary-background-hover: var(--color-charcoal-400);
--secondary-background-selected: var(--color-charcoal-200);
--base-background: var(--color-charcoal-800);
--primary-background: var(--color-azure-600);
--primary-background-hover: var(--color-azure-400);
--destructive-background: var(--color-coral-700);
--destructive-background-hover: var(--color-coral-600);
--inverted-background-hover: var(--color-smoke-200);
--warning-background: var(--color-gold-600);
--warning-background-hover: var(--color-gold-500);
--border-default: var(--color-charcoal-200);
--border-subtle: var(--color-charcoal-300);
--muted-background: var(--color-charcoal-100);
--accent-background: var(--color-charcoal-100);
}
@theme inline {
@@ -268,6 +351,14 @@
--interface-menu-keybind-surface-default
);
--color-interface-panel-surface: var(--interface-panel-surface);
--color-interface-panel-hover-surface: var(--interface-panel-hover-surface);
--color-interface-panel-selected-surface: var(
--interface-panel-selected-surface
);
--color-interface-button-hover-surface: var(
--interface-button-hover-surface
);
--color-comfy-menu-bg: var(--comfy-menu-bg);
--color-interface-stroke: var(--interface-stroke);
--color-nav-background: var(--nav-background);
--color-node-border: var(--node-border);
@@ -312,6 +403,27 @@
--color-text-secondary: var(--text-secondary);
--color-text-primary: var(--text-primary);
--color-input-surface: var(--input-surface);
/* Semantic tokens */
--color-base-foreground: var(--base-foreground);
--color-muted-foreground: var(--muted-foreground);
--color-base-background: var(--base-background);
--color-secondary-background: var(--secondary-background);
--color-secondary-background-hover: var(--secondary-background-hover);
--color-secondary-background-selected: var(--secondary-background-selected);
--color-primary-background: var(--primary-background);
--color-primary-background-hover: var(--primary-background-hover);
--color-destructive-background: var(--destructive-background);
--color-destructive-background-hover: var(--destructive-background-hover);
--color-inverted-background-hover: var(--inverted-background-hover);
--color-warning-background: var(--warning-background);
--color-warning-background-hover: var(--warning-background-hover);
--color-border-default: var(--border-default);
--color-border-subtle: var(--border-subtle);
--color-muted-background: var(--muted-background);
--color-accent-background: var(--accent-background);
--color-brand-yellow: var(--brand-yellow);
--color-brand-blue: var(--brand-blue);
}
@custom-variant dark-theme {
@@ -330,7 +442,6 @@
}
}
/* ===================== Scrollbar Utilities (Tailwind) =====================
Usage: Add `scrollbar-custom` class to scrollable containers.
The scrollbar styling adapts to light/dark theme automatically.

View File

@@ -4,6 +4,13 @@ import { addDynamicIconSelectors } from '@iconify/tailwind'
import { iconCollection } from './src/iconCollection'
export default {
theme: {
extend: {
boxShadow: {
interface: 'var(--interface-panel-box-shadow)'
}
}
},
plugins: [
addDynamicIconSelectors({
iconSets: {

View File

@@ -474,3 +474,13 @@ export function formatDuration(milliseconds: number): string {
return parts.join(' ')
}
/**
* Truncates a filename for display purposes.
* Currently returns the filename as-is since truncation is handled by CSS.
* @param filename The filename to truncate
* @returns The display-ready filename
*/
export function truncateFilename(filename: string): string {
return filename
}

View File

@@ -14,7 +14,7 @@ import { cn } from '@comfyorg/tailwind-utils'
// Use with conditional classes (ternary)
<button
:class="cn('px-4 py-2', isActive ? 'bg-blue-500' : 'bg-gray-500')"
:class="cn('px-4 py-2', isActive ? 'bg-blue-500' : 'bg-smoke-500')"
/>
```

50
pnpm-lock.yaml generated
View File

@@ -87,9 +87,6 @@ catalogs:
'@trivago/prettier-plugin-sort-imports':
specifier: ^5.2.0
version: 5.2.2
'@types/eslint-plugin-tailwindcss':
specifier: ^3.17.0
version: 3.17.0
'@types/fs-extra':
specifier: ^11.0.4
version: 11.0.4
@@ -153,9 +150,6 @@ catalogs:
eslint-plugin-storybook:
specifier: ^9.1.6
version: 9.1.6
eslint-plugin-tailwindcss:
specifier: 4.0.0-beta.0
version: 4.0.0-beta.0
eslint-plugin-unused-imports:
specifier: ^4.2.0
version: 4.2.0
@@ -519,9 +513,6 @@ importers:
'@trivago/prettier-plugin-sort-imports':
specifier: 'catalog:'
version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.6.2)
'@types/eslint-plugin-tailwindcss':
specifier: 'catalog:'
version: 3.17.0
'@types/fs-extra':
specifier: 'catalog:'
version: 11.0.4
@@ -570,9 +561,6 @@ importers:
eslint-plugin-storybook:
specifier: 'catalog:'
version: 9.1.6(eslint@9.35.0(jiti@2.4.2))(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)))(typescript@5.9.2)
eslint-plugin-tailwindcss:
specifier: 'catalog:'
version: 4.0.0-beta.0(tailwindcss@4.1.12)
eslint-plugin-unused-imports:
specifier: 'catalog:'
version: 4.2.0(@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))
@@ -3155,9 +3143,6 @@ packages:
'@types/diff-match-patch@1.0.36':
resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
'@types/eslint-plugin-tailwindcss@3.17.0':
resolution: {integrity: sha512-ucQGf2YIdTcndYcxRU3UdZgmhUHsOlbIF4BaRtl0op+7k2JmqM2i3aXZ6XIcfZgVq1ZKov7VM5c/BR81ukmkyg==}
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
@@ -4700,12 +4685,6 @@ packages:
eslint: '>=8'
storybook: ^9.1.6
eslint-plugin-tailwindcss@4.0.0-beta.0:
resolution: {integrity: sha512-WWCajZgQu38Sd67ZCl2W6i3MRzqB0d+H8s4qV9iB6lBJbsDOIpIlj6R1Fj2FXkoWErbo05pZnZYbCGIU9o/DsA==}
engines: {node: '>=18.12.0'}
peerDependencies:
tailwindcss: ^3.4.0 || ^4.0.0
eslint-plugin-unused-imports@4.2.0:
resolution: {integrity: sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==}
peerDependencies:
@@ -7163,11 +7142,6 @@ packages:
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
engines: {node: '>=10.0.0'}
tailwind-api-utils@1.0.3:
resolution: {integrity: sha512-KpzUHkH1ug1sq4394SLJX38ZtpeTiqQ1RVyFTTSY2XuHsNSTWUkRo108KmyyrMWdDbQrLYkSHaNKj/a3bmA4sQ==}
peerDependencies:
tailwindcss: ^3.3.0 || ^4.0.0 || ^4.0.0-beta
tailwind-merge@2.6.0:
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
@@ -7626,6 +7600,9 @@ packages:
vue-component-type-helpers@3.1.1:
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
vue-component-type-helpers@3.1.3:
resolution: {integrity: sha512-V1dOD8XYfstOKCnXbWyEJIrhTBMwSyNjv271L1Jlx9ExpNlCSuqOs3OdWrGJ0V544zXufKbcYabi/o+gK8lyfQ==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'}
@@ -10308,7 +10285,7 @@ snapshots:
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
type-fest: 2.19.0
vue: 3.5.13(typescript@5.9.2)
vue-component-type-helpers: 3.1.1
vue-component-type-helpers: 3.1.3
'@swc/helpers@0.5.17':
dependencies:
@@ -10613,8 +10590,6 @@ snapshots:
'@types/diff-match-patch@1.0.36': {}
'@types/eslint-plugin-tailwindcss@3.17.0': {}
'@types/estree@1.0.5': {}
'@types/estree@1.0.8': {}
@@ -12380,14 +12355,6 @@ snapshots:
- supports-color
- typescript
eslint-plugin-tailwindcss@4.0.0-beta.0(tailwindcss@4.1.12):
dependencies:
fast-glob: 3.3.3
postcss: 8.5.6
synckit: 0.11.11
tailwind-api-utils: 1.0.3(tailwindcss@4.1.12)
tailwindcss: 4.1.12
eslint-plugin-unused-imports@4.2.0(@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2)):
dependencies:
eslint: 9.35.0(jiti@2.4.2)
@@ -15431,13 +15398,6 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
tailwind-api-utils@1.0.3(tailwindcss@4.1.12):
dependencies:
enhanced-resolve: 5.18.3
jiti: 2.5.1
local-pkg: 1.1.2
tailwindcss: 4.1.12
tailwind-merge@2.6.0: {}
tailwindcss-primeui@0.6.1(tailwindcss@4.1.12):
@@ -15983,6 +15943,8 @@ snapshots:
vue-component-type-helpers@3.1.1: {}
vue-component-type-helpers@3.1.3: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
dependencies:
vue: 3.5.13(typescript@5.9.2)

View File

@@ -30,7 +30,6 @@ catalog:
'@storybook/vue3-vite': ^9.1.1
'@tailwindcss/vite': ^4.1.12
'@trivago/prettier-plugin-sort-imports': ^5.2.0
'@types/eslint-plugin-tailwindcss': ^3.17.0
'@types/fs-extra': ^11.0.4
'@types/jsdom': ^21.1.7
'@types/node': ^20.14.8
@@ -52,7 +51,6 @@ catalog:
eslint-plugin-import-x: ^4.16.1
eslint-plugin-prettier: ^5.5.4
eslint-plugin-storybook: ^9.1.6
eslint-plugin-tailwindcss: 4.0.0-beta.0
eslint-plugin-unused-imports: ^4.2.0
eslint-plugin-vue: ^10.4.0
firebase: ^11.6.0
@@ -64,6 +62,7 @@ catalog:
knip: ^5.62.0
lint-staged: ^15.2.7
markdown-table: ^3.0.4
mixpanel-browser: ^2.71.0
nx: 21.4.1
picocolors: ^1.1.1
pinia: ^2.1.7
@@ -99,7 +98,6 @@ catalog:
zod: ^3.23.8
zod-to-json-schema: ^3.24.1
zod-validation-error: ^3.3.0
mixpanel-browser: ^2.71.0
cleanupUnusedCatalogs: true

View File

@@ -22,7 +22,7 @@
},
"litegraph_base": {
"BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=",
"CLEAR_BACKGROUND_COLOR": "#222",
"CLEAR_BACKGROUND_COLOR": "#141414",
"NODE_TITLE_COLOR": "#999",
"NODE_SELECTED_TITLE_COLOR": "#FFF",
"NODE_TEXT_SIZE": 14,
@@ -52,7 +52,7 @@
"comfy_base": {
"fg-color": "#fff",
"bg-color": "#202020",
"comfy-menu-bg": "#353535",
"comfy-menu-bg": "#171718",
"comfy-menu-secondary-bg": "#303030",
"comfy-input-bg": "#222",
"input-text": "#ddd",

View File

@@ -68,7 +68,12 @@
"content-fg": "#222",
"content-hover-bg": "#adadad",
"content-hover-fg": "#222",
"bar-shadow": "rgba(16, 16, 16, 0.25) 0 0 0.5rem"
"bar-shadow": "rgba(16, 16, 16, 0.25) 0 0 0.5rem",
"interface-panel-box-shadow": "1px 1px 8px 0 rgba(0, 0, 0, 0.2)",
"interface-panel-drop-shadow": "1px 1px 4px rgba(0, 0, 0, 0.4)",
"interface-panel-hover-surface": "var(--color-gray-200)",
"interface-panel-selected-surface": "color-mix(in srgb, var(--interface-panel-surface) 78%, var(--contrast-mix-color))",
"contrast-mix-color": "#000"
}
}
}

View File

@@ -42,7 +42,7 @@
<slot name="topmenu" :sidebar-panel-visible="sidebarPanelVisible" />
<Splitter
class="splitter-overlay splitter-overlay-bottom mr-1 mb-1 ml-1 flex-1"
class="splitter-overlay splitter-overlay-bottom mx-1 mb-1 flex-1"
layout="vertical"
:pt:gutter="
'rounded-tl-lg rounded-tr-lg ' +

View File

@@ -1,7 +1,7 @@
<template>
<div
v-show="workspaceState.focusMode"
class="comfy-menu-hamburger no-drag top-0 right-0"
class="comfy-menu-hamburger no-drag right-0 top-0"
>
<Button
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"

View File

@@ -1,11 +1,11 @@
<template>
<div v-if="!workspaceStore.focusMode" class="ml-2 flex pt-1">
<div v-if="!workspaceStore.focusMode" class="ml-1 flex gap-x-0.5 pt-1">
<div class="min-w-0 flex-1">
<SubgraphBreadcrumb />
</div>
<div
class="actionbar-container pointer-events-auto mx-1 flex h-12 items-center rounded-lg px-2 shadow-md"
class="actionbar-container pointer-events-auto flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] px-2 shadow-interface"
>
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
<div
@@ -48,6 +48,5 @@ onMounted(() => {
<style scoped>
.actionbar-container {
background-color: var(--comfy-menu-bg);
border: 1px solid var(--p-panel-border-color);
}
</style>

View File

@@ -10,7 +10,7 @@
</div>
<Panel
class="pointer-events-auto z-1000"
class="z-1000 pointer-events-auto"
:style="style"
:class="panelClass"
:pt="{
@@ -18,7 +18,7 @@
content: { class: isDocked ? 'p-0' : 'p-1' }
}"
>
<div ref="panelRef" class="flex items-center select-none">
<div ref="panelRef" class="flex select-none items-center">
<span
ref="dragHandleRef"
:class="
@@ -258,7 +258,9 @@ const panelClass = computed(() =>
cn(
'actionbar pointer-events-auto z1000',
isDragging.value && 'select-none pointer-events-none',
isDocked.value ? 'p-0 static mr-2 border-none bg-transparent' : 'fixed'
isDocked.value
? 'p-0 static mr-2 border-none bg-transparent'
: 'fixed shadow-interface'
)
)
</script>

View File

@@ -7,7 +7,7 @@
class="flex flex-col"
>
<h3
class="subcategory-title mb-4 text-xs font-bold tracking-wide text-surface-600 uppercase dark-theme:text-surface-400"
class="subcategory-title text-surface-600 dark-theme:text-surface-400 mb-4 text-xs font-bold uppercase tracking-wide"
>
{{ getSubcategoryTitle(subcategory) }}
</h3>
@@ -16,7 +16,7 @@
<div
v-for="command in subcategoryCommands"
:key="command.id"
class="shortcut-item flex items-center justify-between rounded py-2 transition-colors duration-200 hover:bg-surface-100 dark-theme:hover:bg-surface-700"
class="shortcut-item hover:bg-surface-100 dark-theme:hover:bg-surface-700 flex items-center justify-between rounded py-2 transition-colors duration-200"
>
<div class="shortcut-info grow pr-4">
<div class="shortcut-name text-sm font-medium">
@@ -32,7 +32,7 @@
<span
v-for="key in command.keybinding!.combo.getKeySequences()"
:key="key"
class="key-badge min-w-6 rounded border bg-surface-200 px-2 py-1 text-center font-mono text-xs dark-theme:bg-surface-600"
class="key-badge bg-surface-200 dark-theme:bg-surface-600 min-w-6 rounded border px-2 py-1 text-center font-mono text-xs"
>
{{ formatKey(key) }}
</span>

View File

@@ -1,9 +1,6 @@
<template>
<div
ref="rootEl"
class="relative h-full w-full overflow-hidden bg-neutral-900"
>
<div class="p-terminal h-full w-full rounded-none p-2">
<div ref="rootEl" class="relative size-full overflow-hidden bg-neutral-900">
<div class="p-terminal size-full rounded-none p-2">
<div ref="terminalEl" class="terminal-host h-full" />
</div>
<Button

View File

@@ -1,5 +1,5 @@
<template>
<div class="h-full w-full bg-transparent">
<div class="size-full bg-transparent">
<p v-if="errorMessage" class="p-4 text-center">
{{ errorMessage }}
</p>

View File

@@ -1,6 +1,6 @@
<template>
<div
class="subgraph-breadcrumb w-auto drop-shadow-md"
class="subgraph-breadcrumb w-auto drop-shadow-[var(--interface-panel-drop-shadow)]"
:class="{
'subgraph-breadcrumb-collapse': collapseTabs,
'subgraph-breadcrumb-overflow': overflowingTabs
@@ -201,8 +201,8 @@ onUpdated(() => {
:deep(.p-breadcrumb-separator),
:deep(.p-breadcrumb-item) {
@apply h-12;
border-top: 1px solid var(--p-panel-border-color);
border-bottom: 1px solid var(--p-panel-border-color);
border-top: 1px solid var(--interface-stroke);
border-bottom: 1px solid var(--interface-stroke);
background-color: var(--comfy-menu-bg);
}
@@ -214,7 +214,7 @@ onUpdated(() => {
@apply rounded-l-lg;
/* Then collapse the root workflow */
flex-shrink: 5000;
border-left: 1px solid var(--p-panel-border-color);
border-left: 1px solid var(--interface-stroke);
.p-breadcrumb-item-link {
padding-left: var(--p-breadcrumb-item-padding);
@@ -225,7 +225,7 @@ onUpdated(() => {
@apply rounded-r-lg;
/* Then collapse the active item */
flex-shrink: 1;
border-right: 1px solid var(--p-panel-border-color);
border-right: 1px solid var(--interface-stroke);
}
:deep(.p-breadcrumb-item-link:hover),

View File

@@ -38,7 +38,7 @@
v-if="isEditing"
ref="itemInputRef"
v-model="itemLabel"
class="fixed z-10000 px-2 py-2 text-[.8rem]"
class="z-10000 fixed p-2 text-[.8rem]"
@blur="inputBlur(false)"
@click.stop
@keydown.enter="inputBlur(true)"

View File

@@ -1,5 +1,5 @@
<template>
<div class="line-clamp-2 h-7 text-xs text-zinc-500 dark-theme:text-zinc-400">
<div class="dark-theme:text-zinc-400 line-clamp-2 h-7 text-xs text-zinc-500">
<slot></slot>
</div>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div :class="topStyle">
<slot class="absolute top-0 left-0 h-full w-full"></slot>
<slot class="absolute left-0 top-0 size-full"></slot>
<div v-if="slots['top-left']" :class="slotClasses['top-left']">
<slot name="top-left"></slot>

View File

@@ -2,15 +2,15 @@
<div class="image-upload-wrapper">
<div class="flex items-center gap-2">
<div
class="preview-box flex h-16 w-16 items-center justify-center rounded border p-2"
:class="{ 'bg-gray-100 dark-theme:bg-gray-800': !modelValue }"
class="preview-box flex size-16 items-center justify-center rounded border p-2"
:class="{ 'bg-smoke-100 dark-theme:bg-smoke-800': !modelValue }"
>
<img
v-if="modelValue"
:src="modelValue"
class="max-h-full max-w-full object-contain"
/>
<i v-else class="pi pi-image text-xl text-gray-400" />
<i v-else class="pi pi-image text-smoke-400 text-xl" />
</div>
<div class="flex flex-col gap-2">

View File

@@ -1,7 +1,7 @@
<template>
<div
ref="containerRef"
class="relative flex h-full w-full items-center justify-center overflow-hidden"
class="relative flex size-full items-center justify-center overflow-hidden"
:class="containerClass"
>
<Skeleton
@@ -23,7 +23,7 @@
/>
<div
v-if="hasError"
class="absolute inset-0 flex items-center justify-center bg-surface-50 text-muted dark-theme:bg-surface-800"
class="bg-surface-50 text-muted dark-theme:bg-surface-800 absolute inset-0 flex items-center justify-center"
>
<img
src="/assets/images/default-template.png"

View File

@@ -5,7 +5,7 @@
<div class="flex flex-col items-center">
<i :class="icon" style="font-size: 3rem; margin-bottom: 1rem" />
<h3>{{ title }}</h3>
<p :class="textClass" class="text-center whitespace-pre-line">
<p :class="textClass" class="whitespace-pre-line text-center">
{{ message }}
</p>
<Button

View File

@@ -56,7 +56,7 @@ describe('UserAvatar', () => {
const avatar = wrapper.findComponent(Avatar)
expect(avatar.exists()).toBe(true)
expect(avatar.props('image')).toBeNull()
expect(avatar.props('icon')).toBe('pi pi-user')
expect(avatar.props('icon')).toBe('icon-[lucide--user]')
})
it('renders with default icon when provided photo Url is null', () => {
@@ -67,7 +67,7 @@ describe('UserAvatar', () => {
const avatar = wrapper.findComponent(Avatar)
expect(avatar.exists()).toBe(true)
expect(avatar.props('image')).toBeNull()
expect(avatar.props('icon')).toBe('pi pi-user')
expect(avatar.props('icon')).toBe('icon-[lucide--user]')
})
it('falls back to icon when image fails to load', async () => {
@@ -82,7 +82,7 @@ describe('UserAvatar', () => {
avatar.vm.$emit('error')
await nextTick()
expect(avatar.props('icon')).toBe('pi pi-user')
expect(avatar.props('icon')).toBe('icon-[lucide--user]')
})
it('uses provided ariaLabel', () => {

View File

@@ -1,7 +1,9 @@
<template>
<Avatar
class="bg-interface-panel-selected-surface"
:image="photoUrl ?? undefined"
:icon="hasAvatar ? undefined : 'pi pi-user'"
:icon="hasAvatar ? undefined : 'icon-[lucide--user]'"
:pt:icon:class="{ 'size-4': !hasAvatar }"
shape="circle"
:aria-label="ariaLabel ?? $t('auth.login.userAvatar')"
@error="handleImageError"

View File

@@ -36,7 +36,7 @@
</template>
<template #contentFilter>
<div class="relative flex flex-wrap gap-2 px-6 pt-2 pb-4">
<div class="relative flex flex-wrap gap-2 px-6 pb-4 pt-2">
<!-- Model Filter -->
<MultiSelect
v-model="selectedModelObjects"
@@ -97,7 +97,7 @@
</div>
<div
v-if="!isLoading"
class="text-neutral px-6 pt-4 pb-2 text-2xl font-semibold"
class="text-neutral px-6 pb-2 pt-4 text-2xl font-semibold"
>
<span>
{{ pageTitle }}
@@ -111,7 +111,7 @@
v-if="!isLoading && filteredTemplates.length === 0"
class="flex h-64 flex-col items-center justify-center text-neutral-500"
>
<i class="mb-4 icon-[lucide--search] h-12 w-12 opacity-50" />
<i class="icon-[lucide--search] mb-4 size-12 opacity-50" />
<p class="mb-2 text-lg">
{{ $t('templateWorkflows.noResults', 'No templates found') }}
</p>
@@ -128,7 +128,7 @@
<!-- Title -->
<span
v-if="isLoading"
class="inline-block h-8 w-48 animate-pulse rounded bg-dialog-surface"
class="bg-dialog-surface inline-block h-8 w-48 animate-pulse rounded"
></span>
<!-- Template Cards Grid -->
@@ -144,14 +144,12 @@
size="compact"
variant="ghost"
rounded="lg"
class="hover:bg-white dark-theme:hover:bg-zinc-800"
class="hover:bg-base-background"
>
<template #top>
<CardTop ratio="landscape">
<template #default>
<div
class="h-full w-full animate-pulse bg-dialog-surface"
></div>
<div class="bg-dialog-surface size-full animate-pulse"></div>
</template>
</CardTop>
</template>
@@ -159,10 +157,10 @@
<CardBottom>
<div class="px-4 py-3">
<div
class="mb-2 h-6 animate-pulse rounded bg-dialog-surface"
class="bg-dialog-surface mb-2 h-6 animate-pulse rounded"
></div>
<div
class="h-4 animate-pulse rounded bg-dialog-surface"
class="bg-dialog-surface h-4 animate-pulse rounded"
></div>
</div>
</CardBottom>
@@ -178,7 +176,7 @@
variant="ghost"
rounded="lg"
:data-testid="`template-workflow-${template.name}`"
class="hover:bg-white dark-theme:hover:bg-zinc-800"
class="hover:bg-base-background"
@mouseenter="hoveredTemplate = template.name"
@mouseleave="hoveredTemplate = null"
@click="onLoadWorkflow(template)"
@@ -187,9 +185,7 @@
<CardTop ratio="square">
<template #default>
<!-- Template Thumbnail -->
<div
class="relative h-full w-full overflow-hidden rounded-lg"
>
<div class="relative size-full overflow-hidden rounded-lg">
<template v-if="template.mediaType === 'audio'">
<AudioThumbnail :src="getBaseThumbnailSrc(template)" />
</template>
@@ -252,7 +248,7 @@
</template>
<ProgressSpinner
v-if="loadingTemplate === template.name"
class="absolute inset-0 z-10 m-auto h-12 w-12"
class="absolute inset-0 z-10 m-auto size-12"
/>
</div>
</template>
@@ -289,7 +285,7 @@
<div class="flex justify-between gap-2">
<div class="flex-1">
<p
class="m-0 line-clamp-2 text-sm text-muted"
class="text-muted m-0 line-clamp-2 text-sm"
:title="getTemplateDescription(template)"
>
{{ getTemplateDescription(template) }}
@@ -323,14 +319,12 @@
size="compact"
variant="ghost"
rounded="lg"
class="hover:bg-white dark-theme:hover:bg-zinc-800"
class="hover:bg-base-background"
>
<template #top>
<CardTop ratio="square">
<template #default>
<div
class="h-full w-full animate-pulse bg-dialog-surface"
></div>
<div class="bg-dialog-surface size-full animate-pulse"></div>
</template>
</CardTop>
</template>
@@ -338,10 +332,10 @@
<CardBottom>
<div class="px-4 py-3">
<div
class="mb-2 h-6 animate-pulse rounded bg-dialog-surface"
class="bg-dialog-surface mb-2 h-6 animate-pulse rounded"
></div>
<div
class="h-4 animate-pulse rounded bg-dialog-surface"
class="bg-dialog-surface h-4 animate-pulse rounded"
></div>
</div>
</CardBottom>
@@ -356,7 +350,7 @@
ref="loadTrigger"
class="mt-4 flex h-4 w-full items-center justify-center"
>
<div v-if="isLoadingMore" class="text-sm text-muted">
<div v-if="isLoadingMore" class="text-muted text-sm">
{{ $t('templateWorkflows.loadingMore', 'Loading more...') }}
</div>
</div>
@@ -364,7 +358,7 @@
<!-- Results Summary -->
<div
v-if="!isLoading"
class="mt-6 px-6 text-sm text-neutral-600 dark-theme:text-neutral-400"
class="dark-theme:text-neutral-400 mt-6 px-6 text-sm text-neutral-600"
>
{{
$t('templateWorkflows.resultsCount', {

View File

@@ -1,5 +1,5 @@
<template>
<div class="flex h-110 max-w-96 flex-col gap-4 p-2">
<div class="h-110 flex max-w-96 flex-col gap-4 p-2">
<div class="mb-2 text-2xl font-medium">
{{ t('apiNodesSignInDialog.title') }}
</div>

View File

@@ -30,7 +30,7 @@
<template v-if="reportOpen">
<Divider />
<ScrollPanel class="h-[400px] w-full max-w-[80vw]">
<pre class="break-words whitespace-pre-wrap">{{ reportContent }}</pre>
<pre class="whitespace-pre-wrap break-words">{{ reportContent }}</pre>
</ScrollPanel>
<Divider />
</template>

View File

@@ -3,7 +3,7 @@
v-if="hasMissingCoreNodes"
severity="info"
icon="pi pi-info-circle"
class="mx-2 my-2"
class="m-2"
:pt="{
root: { class: 'flex-col' },
text: { class: 'flex-1' }
@@ -25,7 +25,7 @@
class="ml-4"
>
<div
class="text-sm font-medium text-surface-600 dark-theme:text-surface-400"
class="text-surface-600 dark-theme:text-surface-400 text-sm font-medium"
>
{{
$t('loadWorkflowWarning.coreNodesFromVersion', {
@@ -33,7 +33,7 @@
})
}}
</div>
<div class="ml-4 text-sm text-surface-500 dark-theme:text-surface-500">
<div class="text-surface-500 dark-theme:text-surface-500 ml-4 text-sm">
{{ getUniqueNodeNames(nodes).join(', ') }}
</div>
</div>

View File

@@ -8,7 +8,7 @@
<template v-else>
<!-- Header -->
<div class="mb-8 flex flex-col gap-4">
<h1 class="my-0 text-2xl leading-normal font-medium">
<h1 class="my-0 text-2xl font-medium leading-normal">
{{ isSignIn ? t('auth.login.title') : t('auth.signup.title') }}
</h1>
<p class="my-0 text-base">
@@ -88,12 +88,12 @@
>
<img
src="/assets/images/comfy-logo-mono.svg"
class="mr-2 h-5 w-5"
class="mr-2 size-5"
:alt="$t('g.comfy')"
/>
{{ t('auth.login.useApiKey') }}
</Button>
<small class="text-center text-muted">
<small class="text-muted text-center">
{{ t('auth.apiKey.helpText') }}
<a
:href="`${COMFY_PLATFORM_BASE_URL}/login`"
@@ -115,7 +115,7 @@
</div>
<!-- Terms & Contact -->
<p class="mt-8 text-xs text-muted">
<p class="text-muted mt-8 text-xs">
{{ t('auth.login.termsText') }}
<a
href="https://www.comfy.org/terms-of-service"

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex w-96 flex-col gap-10 p-2">
<div v-if="isInsufficientCredits" class="flex flex-col gap-4">
<h1 class="my-0 text-2xl leading-normal font-medium">
<h1 class="my-0 text-2xl font-medium leading-normal">
{{ $t('credits.topUp.insufficientTitle') }}
</h1>
<p class="my-0 text-base">
@@ -12,7 +12,7 @@
<!-- Balance Section -->
<div class="flex items-center justify-between">
<div class="flex w-full flex-col gap-2">
<div class="text-base text-muted">
<div class="text-muted text-base">
{{ $t('credits.yourCreditBalance') }}
</div>
<div class="flex w-full items-center justify-between">
@@ -30,7 +30,7 @@
<!-- Amount Input Section -->
<div class="flex flex-col gap-2">
<span class="text-sm text-muted"
<span class="text-muted text-sm"
>{{ $t('credits.topUp.quickPurchase') }}:</span
>
<div class="grid grid-cols-[2fr_1fr] gap-2">

View File

@@ -21,7 +21,7 @@
/>
<span v-else class="text-xl">{{ amount }}</span>
</div>
<ProgressSpinner v-if="loading" class="h-8 w-8" />
<ProgressSpinner v-if="loading" class="size-8" />
<Button
v-else
:severity="preselected ? 'primary' : 'secondary'"

View File

@@ -8,7 +8,7 @@
<Divider />
<div class="flex flex-col gap-2">
<h3 class="text-sm font-medium text-muted">
<h3 class="text-muted text-sm font-medium">
{{ $t('credits.yourCreditBalance') }}
</h3>
<div class="flex items-center justify-between">
@@ -28,7 +28,7 @@
height="1rem"
class="text-xs"
/>
<div v-else-if="formattedLastUpdateTime" class="text-xs text-muted">
<div v-else-if="formattedLastUpdateTime" class="text-muted text-xs">
{{ $t('credits.lastUpdated') }}: {{ formattedLastUpdateTime }}
</div>
<Button
@@ -59,7 +59,7 @@
<Column field="title" :header="$t('g.name')">
<template #body="{ data }">
<div class="text-sm font-medium">{{ data.title }}</div>
<div class="text-xs text-muted">{{ data.timestamp }}</div>
<div class="text-muted text-xs">{{ data.timestamp }}</div>
</template>
</Column>
<Column field="amount" :header="$t('g.amount')">

View File

@@ -1,6 +1,6 @@
<template>
<TabPanel :value="props.value" class="h-full w-full" :class="props.class">
<div class="flex h-full w-full flex-col gap-2">
<TabPanel :value="props.value" class="size-full" :class="props.class">
<div class="flex size-full flex-col gap-2">
<slot name="header" />
<ScrollPanel class="h-0 grow pr-2">
<slot />

View File

@@ -50,7 +50,7 @@
<div class="font-semibold">
{{ data.params?.api_name || 'API' }}
</div>
<div class="text-sm text-gray-400">
<div class="text-smoke-400 text-sm">
{{ $t('credits.model') }}: {{ data.params?.model || '-' }}
</div>
</div>

View File

@@ -35,7 +35,7 @@
<h3 class="font-medium">
{{ $t('userSettings.provider') }}
</h3>
<div class="flex items-center gap-1 text-muted">
<div class="text-muted flex items-center gap-1">
<i :class="providerIcon" />
{{ providerName }}
<Button
@@ -54,7 +54,7 @@
<ProgressSpinner
v-if="loading"
class="mt-4 h-8 w-8"
class="mt-4 size-8"
style="--pc-spinner-color: #000"
/>
<div v-else class="mt-4 flex flex-col gap-2">
@@ -78,7 +78,7 @@
<!-- Login Section -->
<div v-else class="flex flex-col gap-4">
<p class="text-gray-600">
<p class="text-smoke-600">
{{ $t('auth.login.title') }}
</p>

View File

@@ -1,11 +1,11 @@
<template>
<div class="flex flex-col gap-6">
<div class="mb-8 flex flex-col gap-4">
<h1 class="my-0 text-2xl leading-normal font-medium">
<h1 class="my-0 text-2xl font-medium leading-normal">
{{ t('auth.apiKey.title') }}
</h1>
<div class="flex flex-col gap-2">
<p class="my-0 text-base text-muted">
<p class="text-muted my-0 text-base">
{{ t('auth.apiKey.description') }}
</p>
<a

View File

@@ -34,7 +34,7 @@
{{ t('auth.login.passwordLabel') }}
</label>
<span
class="cursor-pointer text-base font-medium text-muted select-none"
class="text-muted cursor-pointer select-none text-base font-medium"
:class="{
'text-link-disabled': !$form.email?.value || $form.email?.invalid
}"
@@ -60,7 +60,7 @@
</div>
<!-- Submit Button -->
<ProgressSpinner v-if="loading" class="h-8 w-8" />
<ProgressSpinner v-if="loading" class="size-8" />
<Button
v-else
type="submit"

View File

@@ -2,18 +2,18 @@
<Button
ref="buttonRef"
severity="secondary"
class="group h-8 rounded-none! bg-interface-panel-surface p-0 transition-none! hover:rounded-lg! hover:bg-button-hover-surface!"
class="group h-8 rounded-none! bg-comfy-menu-bg p-0 transition-none! hover:rounded-lg! hover:bg-interface-button-hover-surface!"
:style="buttonStyles"
@click="toggle"
>
<template #default>
<div class="flex items-center gap-1 pr-0.5">
<div
class="rounded-lg bg-button-active-surface p-2 group-hover:bg-button-hover-surface"
class="rounded-lg bg-interface-panel-selected-surface p-2 group-hover:bg-interface-button-hover-surface"
>
<i :class="currentModeIcon" class="block h-4 w-4" />
<i :class="currentModeIcon" class="block size-4" />
</div>
<i class="icon-[lucide--chevron-down] block h-4 w-4 pr-1.5" />
<i class="icon-[lucide--chevron-down] block size-4 pr-1.5" />
</div>
</template>
</Button>
@@ -29,27 +29,27 @@
>
<div class="flex flex-col gap-1">
<div
class="flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
class="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between px-3 py-2 text-sm"
@click="setMode('select')"
>
<div class="flex items-center gap-2">
<i class="icon-[lucide--mouse-pointer-2] h-4 w-4" />
<i class="icon-[lucide--mouse-pointer-2] size-4" />
<span>{{ $t('graphCanvasMenu.select') }}</span>
</div>
<span class="text-[9px] text-text-primary">{{
<span class="text-text-primary text-[9px]">{{
unlockCommandText
}}</span>
</div>
<div
class="flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
class="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm"
@click="setMode('hand')"
>
<div class="flex items-center gap-2">
<i class="icon-[lucide--hand] h-4 w-4" />
<i class="icon-[lucide--hand] size-4" />
<span>{{ $t('graphCanvasMenu.hand') }}</span>
</div>
<span class="text-[9px] text-text-primary">{{ lockCommandText }}</span>
<span class="text-text-primary text-[9px]">{{ lockCommandText }}</span>
</div>
</div>
</Popover>
@@ -114,7 +114,7 @@ const popoverPt = computed(() => ({
content: {
class: [
'mb-2 text-text-primary',
'shadow-lg border border-node-border',
'shadow-lg border border-interface-stroke',
'bg-nav-background',
'rounded-lg',
'p-2 px-3',

View File

@@ -5,12 +5,12 @@
<LiteGraphCanvasSplitterOverlay v-if="comfyAppReady">
<template v-if="showUI && workflowTabsPosition === 'Topbar'" #workflow-tabs>
<div
class="workflow-tabs-container pointer-events-auto relative h-9.5 w-full"
class="workflow-tabs-container h-9.5 pointer-events-auto relative w-full"
>
<!-- Native drag area for Electron -->
<div
v-if="isNativeWindow() && workflowTabsPosition !== 'Topbar'"
class="app-drag fixed top-0 left-0 z-10 h-[var(--comfy-topbar-height)] w-full"
class="app-drag fixed left-0 top-0 z-10 h-[var(--comfy-topbar-height)] w-full"
/>
<div class="flex h-full items-center">
<WorkflowTabs />
@@ -23,7 +23,7 @@
</template>
<template v-if="showUI" #side-bar-panel>
<div
class="sidebar-content-container h-full w-full overflow-x-hidden overflow-y-auto"
class="sidebar-content-container size-full overflow-y-auto overflow-x-hidden"
>
<ExtensionSlot v-if="activeSidebarTab" :extension="activeSidebarTab" />
</div>

View File

@@ -5,20 +5,22 @@
<!-- Backdrop -->
<div
v-if="hasActivePopup"
class="fixed inset-0 z-1200"
class="z-1200 fixed inset-0"
@click="hideModal"
></div>
<ButtonGroup
class="absolute right-0 bottom-0 z-[1200] flex-row gap-1 border-[1px] border-node-border bg-interface-panel-surface p-2"
:style="stringifiedMinimapStyles.buttonGroupStyles"
class="absolute right-0 bottom-0 z-[1200] flex-row gap-1 border-[1px] border-interface-stroke bg-comfy-menu-bg p-2"
:style="{
...stringifiedMinimapStyles.buttonGroupStyles
}"
@wheel="canvasInteractions.handleWheel"
>
<CanvasModeSelector
:button-styles="stringifiedMinimapStyles.buttonStyles"
/>
<div class="h-[27px] w-[1px] self-center bg-node-divider" />
<div class="bg-node-divider h-[27px] w-px self-center" />
<Button
v-tooltip.top="fitViewTooltip"
@@ -26,11 +28,11 @@
icon="pi pi-expand"
:aria-label="fitViewTooltip"
:style="stringifiedMinimapStyles.buttonStyles"
class="h-8 w-8 bg-interface-panel-surface p-0 hover:bg-button-hover-surface!"
class="h-8 w-8 bg-comfy-menu-bg p-0 hover:bg-interface-button-hover-surface!"
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
>
<template #icon>
<i class="icon-[lucide--focus] h-4 w-4" />
<i class="icon-[lucide--focus] size-4" />
</template>
</Button>
@@ -47,11 +49,11 @@
>
<span class="inline-flex items-center gap-1 px-2 text-xs">
<span>{{ canvasStore.appScalePercentage }}%</span>
<i class="icon-[lucide--chevron-down] h-4 w-4" />
<i class="icon-[lucide--chevron-down] size-4" />
</span>
</Button>
<div class="h-[27px] w-[1px] self-center bg-node-divider" />
<div class="bg-node-divider h-[27px] w-px self-center" />
<Button
ref="minimapButton"
@@ -64,7 +66,7 @@
@click="() => commandStore.execute('Comfy.Canvas.ToggleMinimap')"
>
<template #icon>
<i class="icon-[lucide--map] h-4 w-4" />
<i class="icon-[lucide--map] size-4" />
</template>
</Button>
@@ -85,7 +87,7 @@
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
>
<template #icon>
<i class="icon-[lucide--route-off] h-4 w-4" />
<i class="icon-[lucide--route-off] size-4" />
</template>
</Button>
</ButtonGroup>
@@ -163,18 +165,18 @@ const minimapCommandText = computed(() =>
// Computed properties for button classes and states
const zoomButtonClass = computed(() => [
'bg-interface-panel-surface',
isModalVisible.value ? 'not-active:bg-button-active-surface!' : '',
'hover:bg-button-hover-surface!',
'bg-comfy-menu-bg',
isModalVisible.value ? 'not-active:bg-interface-panel-selected-surface!' : '',
'hover:bg-interface-button-hover-surface!',
'p-0',
'h-8',
'w-15'
])
const minimapButtonClass = computed(() => ({
'bg-interface-panel-surface': true,
'hover:bg-button-hover-surface!': true,
'not-active:bg-button-active-surface!': settingStore.get(
'bg-comfy-menu-bg': true,
'hover:bg-interface-button-hover-surface!': true,
'not-active:bg-interface-panel-selected-surface!': settingStore.get(
'Comfy.Minimap.Visible'
),
'p-0': true,
@@ -206,9 +208,9 @@ const linkVisibilityAriaLabel = computed(() =>
: t('graphCanvasMenu.hideLinks')
)
const linkVisibleClass = computed(() => [
'bg-interface-panel-surface',
linkHidden.value ? 'not-active:bg-button-active-surface!' : '',
'hover:bg-button-hover-surface!',
'bg-comfy-menu-bg',
linkHidden.value ? 'not-active:bg-interface-panel-selected-surface!' : '',
'hover:bg-interface-button-hover-surface!',
'p-0',
'w-8',
'h-8'

View File

@@ -2,12 +2,12 @@
<div
ref="toolboxRef"
style="transform: translate(var(--tb-x), var(--tb-y))"
class="pointer-events-none fixed top-0 left-0 z-40"
class="pointer-events-none fixed left-0 top-0 z-40"
>
<Transition name="slide-up">
<Panel
v-if="visible"
class="selection-toolbox pointer-events-auto rounded-lg border border-interface-stroke bg-interface-panel-surface"
class="selection-toolbox border-interface-stroke bg-interface-panel-surface pointer-events-auto rounded-lg border"
:pt="{
header: 'hidden',
content: 'p-2 h-12 flex flex-row gap-1'

View File

@@ -1,51 +1,51 @@
<template>
<div
v-if="visible"
class="absolute right-0 bottom-[62px] z-1300 flex w-[250px] justify-center border-0! bg-inherit!"
class="z-1300 border-0! bg-inherit! absolute bottom-[62px] right-0 flex w-[250px] justify-center"
>
<div
class="w-4/5 rounded-lg border border-node-border bg-interface-panel-surface p-2 text-text-primary shadow-lg select-none"
class="w-4/5 rounded-lg border border-interface-stroke bg-interface-panel-surface p-2 text-text-primary shadow-lg select-none"
:style="filteredMinimapStyles"
@click.stop
>
<div class="flex flex-col gap-1">
<div
class="flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
class="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm"
@mousedown="startRepeat('Comfy.Canvas.ZoomIn')"
@mouseup="stopRepeat"
@mouseleave="stopRepeat"
>
<span class="font-medium">{{ $t('graphCanvasMenu.zoomIn') }}</span>
<span class="text-[9px] text-text-primary">{{
<span class="text-text-primary text-[9px]">{{
zoomInCommandText
}}</span>
</div>
<div
class="flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
class="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm"
@mousedown="startRepeat('Comfy.Canvas.ZoomOut')"
@mouseup="stopRepeat"
@mouseleave="stopRepeat"
>
<span class="font-medium">{{ $t('graphCanvasMenu.zoomOut') }}</span>
<span class="text-[9px] text-text-primary">{{
<span class="text-text-primary text-[9px]">{{
zoomOutCommandText
}}</span>
</div>
<div
class="flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
class="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm"
@click="executeCommand('Comfy.Canvas.FitView')"
>
<span class="font-medium">{{ $t('zoomControls.zoomToFit') }}</span>
<span class="text-[9px] text-text-primary">{{
<span class="text-text-primary text-[9px]">{{
zoomToFitCommandText
}}</span>
</div>
<div
ref="zoomInputContainer"
class="zoomInputContainer flex items-center gap-1 rounded bg-input-surface p-2"
class="zoomInputContainer bg-input-surface flex items-center gap-1 rounded p-2"
>
<InputNumber
ref="zoomInput"
@@ -60,7 +60,7 @@
@input="applyZoom"
@keyup.enter="applyZoom"
/>
<span class="flex-shrink-0 text-sm text-text-primary">%</span>
<span class="text-text-primary shrink-0 text-sm">%</span>
</div>
</div>
</div>
@@ -148,6 +148,6 @@ watch(
</script>
<style>
.zoomInputContainer:focus-within {
border: 1px solid var(--color-pure-white);
border: 1px solid var(--color-white);
}
</style>

View File

@@ -92,7 +92,7 @@ describe('BypassButton', () => {
const button = wrapper.find('button')
expect(button.classes()).not.toContain(
'dark-theme:[&:not(:active)]:!bg-[#262729]'
'dark-theme:[&:not(:active)]:!bg-charcoal-600'
)
})

View File

@@ -7,11 +7,11 @@
severity="secondary"
text
data-testid="bypass-button"
class="hover:bg-[#E7E6E6] hover:dark-theme:bg-charcoal-600"
class="hover:bg-secondary-background"
@click="toggleBypass"
>
<template #icon>
<i class="icon-[lucide--ban] h-4 w-4" />
<i class="icon-[lucide--ban] size-4" />
</template>
</Button>
</template>

View File

@@ -12,11 +12,11 @@
>
<div class="flex items-center gap-1 px-0">
<i
class="pi pi-circle-fill h-4 w-4"
class="pi pi-circle-fill size-4"
:style="{ color: currentColor ?? '' }"
/>
<i
class="pi pi-chevron-down h-4 w-4 py-1"
class="pi pi-chevron-down size-4 py-1"
:style="{ fontSize: '0.5rem' }"
/>
</div>

View File

@@ -11,7 +11,7 @@
@click="() => commandStore.execute('Comfy.Graph.UnpackSubgraph')"
>
<template #icon>
<i class="icon-[lucide--expand] h-4 w-4" />
<i class="icon-[lucide--expand] size-4" />
</template>
</Button>
<Button

View File

@@ -4,7 +4,7 @@
value: t('selectionToolbox.executeButton.tooltip'),
showDelay: 1000
}"
class="size-8 bg-[#31B9F4] !p-0 dark-theme:bg-[#0B8CE9]"
class="bg-azure-400 dark-theme:bg-azure-600 size-8 !p-0"
text
@mouseenter="() => handleMouseEnter()"
@mouseleave="() => handleMouseLeave()"

View File

@@ -9,7 +9,7 @@
severity="secondary"
@click="frameNodes"
>
<i class="icon-[lucide--frame] h-4 w-4" />
<i class="icon-[lucide--frame] size-4" />
</Button>
</template>

View File

@@ -9,7 +9,7 @@
severity="secondary"
@click="toggleHelp"
>
<i class="icon-[lucide--info] h-4 w-4" />
<i class="icon-[lucide--info] size-4" />
</Button>
</template>

View File

@@ -1,19 +1,19 @@
<template>
<div
v-if="option.type === 'divider'"
class="my-1 h-px bg-gray-200 dark-theme:bg-zinc-700"
class="bg-smoke-200 dark-theme:bg-zinc-700 my-1 h-px"
/>
<div
v-else
role="button"
class="group flex cursor-pointer items-center gap-2 rounded px-3 py-1.5 text-left text-sm text-text-primary hover:bg-interface-menu-component-surface-hovered"
class="text-text-primary hover:bg-interface-menu-component-surface-hovered group flex cursor-pointer items-center gap-2 rounded px-3 py-1.5 text-left text-sm"
@click="handleClick"
>
<i v-if="option.icon" :class="[option.icon, 'h-4 w-4']" />
<i v-if="option.icon" :class="[option.icon, 'size-4']" />
<span class="flex-1">{{ option.label }}</span>
<span
v-if="option.shortcut"
class="flex h-3.5 min-w-3.5 items-center justify-center rounded bg-interface-menu-keybind-surface-default px-1 py-0 text-xxs"
class="bg-interface-menu-keybind-surface-default text-xxs flex h-3.5 min-w-3.5 items-center justify-center rounded px-1 py-0"
>
{{ option.shortcut }}
</span>
@@ -27,11 +27,11 @@
:severity="option.badge === 'new' ? 'info' : 'secondary'"
:value="t(option.badge)"
:class="{
'rounded-4xl bg-[#31B9F4] dark-theme:bg-[#0B8CE9]':
'rounded-4xl bg-azure-400 dark-theme:bg-azure-600':
option.badge === 'new',
'rounded-4xl bg-[#9C9EAB] dark-theme:bg-[#000]':
'rounded-4xl dark-theme:bg-black bg-slate-100':
option.badge === 'deprecated',
'h-4 gap-2.5 px-1 text-[9px] text-white uppercase': true
'h-4 gap-2.5 px-1 text-[9px] uppercase text-white': true
}"
/>
</div>

View File

@@ -7,11 +7,11 @@
}"
data-testid="more-options-button"
text
class="h-8 w-8 px-0"
class="size-8 px-0"
severity="secondary"
@click="handleClick"
>
<i class="icon-[lucide--more-vertical] h-4 w-4" />
<i class="icon-[lucide--more-vertical] size-4" />
</Button>
</template>

View File

@@ -7,7 +7,7 @@
data-testid="refresh-button"
@click="refreshSelected"
>
<i class="icon-[lucide--refresh-cw] h-4 w-4" />
<i class="icon-[lucide--refresh-cw] size-4" />
</Button>
</template>

View File

@@ -20,23 +20,23 @@
:key="subOption.label"
:class="
isColorSubmenu
? 'w-7 h-7 flex items-center justify-center hover:bg-gray-100 dark-theme:hover:bg-zinc-700 rounded cursor-pointer'
: 'flex items-center gap-2 px-3 py-1.5 text-sm hover:bg-gray-100 dark-theme:hover:bg-zinc-700 rounded cursor-pointer'
? 'w-7 h-7 flex items-center justify-center hover:bg-smoke-100 dark-theme:hover:bg-zinc-700 rounded cursor-pointer'
: 'flex items-center gap-2 px-3 py-1.5 text-sm hover:bg-smoke-100 dark-theme:hover:bg-zinc-700 rounded cursor-pointer'
"
:title="subOption.label"
@click="handleSubmenuClick(subOption)"
>
<div
v-if="subOption.color"
class="h-5 w-5 rounded-full border border-gray-300 dark-theme:border-zinc-600"
class="border-smoke-300 dark-theme:border-zinc-600 size-5 rounded-full border"
:style="{ backgroundColor: subOption.color }"
/>
<template v-else-if="!subOption.color">
<i
v-if="isShapeSelected(subOption)"
class="icon-[lucide--check] h-4 w-4 flex-shrink-0"
class="icon-[lucide--check] size-4 shrink-0"
/>
<div v-else class="w-4 flex-shrink-0" />
<div v-else class="w-4 shrink-0" />
<span>{{ subOption.label }}</span>
</template>
</div>

View File

@@ -1,3 +1,5 @@
<template>
<div class="h-6 w-px self-center bg-gray-300/10 dark-theme:bg-gray-600/10" />
<div
class="bg-smoke-300/10 dark-theme:bg-smoke-600/10 h-6 w-px self-center"
/>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<ScrollPanel
ref="scrollPanelRef"
class="min-h-[400px] w-full rounded-lg px-2 py-2 text-xs"
class="min-h-[400px] w-full rounded-lg p-2 text-xs"
:pt="{ content: { id: 'chat-scroll-content' } }"
>
<div v-for="(item, i) in parsedHistory" :key="i" class="mb-4">
@@ -13,12 +13,12 @@
>
<div class="mb-1 flex justify-end">
<div
class="max-w-[80%] rounded-xl bg-gray-300 px-4 py-1 text-right dark-theme:bg-gray-800"
class="bg-smoke-300 dark-theme:bg-smoke-800 max-w-[80%] rounded-xl px-4 py-1 text-right"
>
<div class="text-[12px] break-words">{{ item.prompt }}</div>
<div class="break-words text-[12px]">{{ item.prompt }}</div>
</div>
</div>
<div class="mr-1 mb-2 flex justify-end">
<div class="mb-2 mr-1 flex justify-end">
<CopyButton :text="item.prompt" />
<Button
v-tooltip="
@@ -26,7 +26,7 @@
"
text
rounded
class="h-4! w-4! p-1! text-gray-400 transition hover:text-gray-600 hover:dark-theme:text-gray-200"
class="h-4! w-4! p-1! text-smoke-400 hover:text-smoke-600 hover:dark-theme:text-smoke-200 transition"
pt:icon:class="text-xs!"
:icon="editIndex === i ? 'pi pi-times' : 'pi pi-pencil'"
:aria-label="

View File

@@ -5,7 +5,7 @@
"
text
rounded
class="h-4! w-6! p-1! text-gray-400 transition hover:text-gray-600 hover:dark-theme:text-gray-200"
class="h-4! w-6! p-1! text-smoke-400 hover:text-smoke-600 hover:dark-theme:text-smoke-200 transition"
pt:icon:class="text-xs!"
:icon="copied ? 'pi pi-check' : 'pi pi-copy'"
:aria-label="

View File

@@ -2,7 +2,7 @@
<span>
<div class="mb-1 flex justify-start">
<div class="max-w-[80%] rounded-xl px-4 py-1">
<div class="text-[12px] break-words">
<div class="break-words text-[12px]">
<slot />
</div>
</div>

View File

@@ -4,13 +4,11 @@
:width="size"
:height="size"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.8193 0.600586C15.1248 0.600586 15.3296 0.70893 15.459 0.881836C15.5914 1.05888 15.6471 1.33774 15.5527 1.66895L14.8037 4.30176C14.7063 4.64386 14.4729 4.97024 14.1641 5.21191C13.8544 5.45415 13.496 5.58984 13.1699 5.58984H13.1689L9.5791 5.59668H7.90625C7.52654 5.59668 7.19496 5.84986 7.09082 6.21289L5.69434 11.0889C5.63007 11.3133 5.66134 11.5534 5.77734 11.7529L5.83203 11.8359C5.99177 12.0491 6.24252 12.1758 6.50977 12.1758H6.51074L8.88281 12.1709H11.4971C11.7643 12.171 11.9541 12.254 12.084 12.3906L12.1357 12.4521C12.2685 12.6295 12.3249 12.9089 12.2305 13.2402L11.4805 15.8721C11.383 16.2144 11.1498 16.5415 10.8408 16.7832C10.5314 17.0252 10.1736 17.161 9.84766 17.1611H9.84668L6.25684 17.168H3.64258C3.33762 17.1679 3.13349 17.0588 3.00391 16.8857C2.87135 16.7087 2.81482 16.43 2.90918 16.0986L3.39551 14.3887C3.46841 14.1327 3.41794 13.8576 3.25879 13.6445V13.6436C3.09901 13.4303 2.84745 13.3037 2.58008 13.3037H1.18066C0.875088 13.3037 0.670398 13.1953 0.541016 13.0225C0.408483 12.8451 0.351891 12.5655 0.446289 12.2344L2.11914 6.38965L2.30371 5.74707V5.74609C2.40139 5.40341 2.63456 5.07671 2.94336 4.83496C3.25302 4.59258 3.61143 4.45705 3.9375 4.45703H5.6123C5.94484 4.45703 6.24083 4.26316 6.37891 3.9707L6.42773 3.83984L6.98145 1.89551C7.07894 1.55317 7.31212 1.22614 7.62109 0.984375C7.93074 0.742127 8.2892 0.606445 8.61523 0.606445H8.61621L12.1982 0.600586H14.8193Z"
:stroke="color"
stroke-width="1"
v-bind="attributes"
/>
</svg>
</template>
@@ -22,11 +20,18 @@ interface Props {
size?: number | string
color?: string
class?: string
mode?: 'outline' | 'fill'
}
const {
size = 16,
color = 'currentColor',
mode = 'outline',
class: className
} = defineProps<Props>()
const iconClass = computed(() => className || '')
const attributes = computed(() => ({
stroke: mode === 'outline' ? color : undefined,
strokeWidth: mode === 'outline' ? 1 : undefined,
fill: mode === 'fill' ? color : 'none'
}))
</script>

View File

@@ -119,12 +119,12 @@ export const KeyboardNavigationDemo: Story = {
},
template: `
<div class="space-y-4 p-4">
<div class="bg-blue-50 dark-theme:bg-blue-900/20 border border-blue-200 dark-theme:border-blue-700 rounded-lg p-4">
<div class="bg-blue-50 dark-theme:bg-blue-900/20 border border-azure-400 dark-theme:border-blue-700 rounded-lg p-4">
<h3 class="text-lg font-semibold mb-2">🎯 Keyboard Navigation Test</h3>
<p class="text-sm text-gray-600 dark-theme:text-gray-300 mb-4">
<p class="text-sm text-smoke-600 dark-theme:text-smoke-300 mb-4">
Use your keyboard to navigate this MultiSelect:
</p>
<ol class="text-sm text-gray-600 list-decimal list-inside space-y-1">
<ol class="text-sm text-smoke-600 list-decimal list-inside space-y-1">
<li><strong>Tab</strong> to focus the dropdown</li>
<li><strong>Enter/Space</strong> to open dropdown</li>
<li><strong>Arrow Up/Down</strong> to navigate options</li>
@@ -134,11 +134,11 @@ export const KeyboardNavigationDemo: Story = {
</div>
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700">
<label class="block text-sm font-medium text-smoke-700">
Select Frameworks (Keyboard Navigation Test)
</label>
<MultiSelect v-bind="args" class="w-80" />
<p class="text-xs text-gray-500">
<p class="text-xs text-smoke-500">
Selected: {{ selectedFrameworks.map(f => f.name).join(', ') || 'None' }}
</p>
</div>
@@ -186,10 +186,10 @@ export const ScreenReaderFriendly: Story = {
<div class="space-y-6 p-4">
<div class="bg-green-50 dark-theme:bg-green-900/20 border border-green-200 dark-theme:border-green-700 rounded-lg p-4">
<h3 class="text-lg font-semibold mb-2">♿ Screen Reader Test</h3>
<p class="text-sm text-gray-600 mb-2">
<p class="text-sm text-smoke-600 mb-2">
These dropdowns have proper ARIA attributes and labels for screen readers:
</p>
<ul class="text-sm text-gray-600 list-disc list-inside space-y-1">
<ul class="text-sm text-smoke-600 list-disc list-inside space-y-1">
<li><code>role="combobox"</code> identifies as dropdown</li>
<li><code>aria-haspopup="listbox"</code> indicates popup type</li>
<li><code>aria-expanded</code> shows open/closed state</li>
@@ -200,7 +200,7 @@ export const ScreenReaderFriendly: Story = {
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700">
<label class="block text-sm font-medium text-smoke-700">
Color Preferences
</label>
<MultiSelect
@@ -211,13 +211,13 @@ export const ScreenReaderFriendly: Story = {
:show-clear-button="true"
class="w-full"
/>
<p class="text-xs text-gray-500" aria-live="polite">
<p class="text-xs text-smoke-500" aria-live="polite">
{{ selectedColors.length }} color(s) selected
</p>
</div>
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700">
<label class="block text-sm font-medium text-smoke-700">
Size Preferences
</label>
<MultiSelect
@@ -228,7 +228,7 @@ export const ScreenReaderFriendly: Story = {
:show-search-box="true"
class="w-full"
/>
<p class="text-xs text-gray-500" aria-live="polite">
<p class="text-xs text-smoke-500" aria-live="polite">
{{ selectedSizes.length }} size(s) selected
</p>
</div>
@@ -259,25 +259,25 @@ export const FocusManagement: Story = {
<div class="space-y-4 p-4">
<div class="bg-purple-50 dark-theme:bg-purple-900/20 border border-purple-200 dark-theme:border-purple-700 rounded-lg p-4">
<h3 class="text-lg font-semibold mb-2">🎯 Focus Management Test</h3>
<p class="text-sm text-gray-600 dark-theme:text-gray-300 mb-4">
<p class="text-sm text-smoke-600 dark-theme:text-smoke-300 mb-4">
Test focus behavior with multiple form elements:
</p>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
<label class="block text-sm font-medium text-smoke-700 mb-1">
Before MultiSelect
</label>
<input
type="text"
placeholder="Previous field"
class="block w-64 px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="block w-64 px-3 py-2 border border-smoke-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
<label class="block text-sm font-medium text-smoke-700 mb-1">
MultiSelect (Test Focus Ring)
</label>
<MultiSelect
@@ -290,13 +290,13 @@ export const FocusManagement: Story = {
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
<label class="block text-sm font-medium text-smoke-700 mb-1">
After MultiSelect
</label>
<input
type="text"
placeholder="Next field"
class="block w-64 px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="block w-64 px-3 py-2 border border-smoke-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
@@ -307,7 +307,7 @@ export const FocusManagement: Story = {
</button>
</div>
<div class="text-sm text-gray-600 mt-4">
<div class="text-sm text-smoke-600 mt-4">
<strong>Test:</strong> Tab through all elements and verify focus rings are visible and logical.
</div>
</div>
@@ -319,7 +319,7 @@ export const AccessibilityChecklist: Story = {
render: () => ({
template: `
<div class="max-w-4xl mx-auto p-6 space-y-6">
<div class="bg-gray-50 dark-theme:bg-zinc-800 border border-gray-200 dark-theme:border-zinc-700 rounded-lg p-6">
<div class="bg-gray-50 dark-theme:bg-zinc-800 border border-smoke-200 dark-theme:border-zinc-700 rounded-lg p-6">
<h2 class="text-2xl font-bold mb-4">♿ MultiSelect Accessibility Checklist</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@@ -366,9 +366,9 @@ export const AccessibilityChecklist: Story = {
</div>
</div>
<div class="mt-6 p-4 bg-blue-50 dark-theme:bg-blue-900/20 border border-blue-200 dark-theme:border-blue-700 rounded-lg">
<div class="mt-6 p-4 bg-blue-50 dark-theme:bg-blue-900/20 border border-azure-400 dark-theme:border-blue-700 rounded-lg">
<h4 class="font-semibold mb-2">🎯 Quick Test</h4>
<p class="text-sm text-gray-700 dark-theme:text-gray-300">
<p class="text-sm text-smoke-700 dark-theme:text-smoke-300">
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!
</p>

View File

@@ -24,7 +24,7 @@
v-if="showSearchBox || showSelectedCount || showClearButton"
#header
>
<div class="flex flex-col px-2 pt-2 pb-0">
<div class="flex flex-col px-2 pb-0 pt-2">
<SearchBox
v-if="showSearchBox"
v-model="searchQuery"
@@ -39,7 +39,7 @@
>
<span
v-if="showSelectedCount"
class="px-1 text-sm text-neutral-400 dark-theme:text-zinc-500"
class="dark-theme:text-zinc-500 px-1 text-sm text-neutral-400"
>
{{
selectedCount > 0
@@ -52,22 +52,22 @@
:label="$t('g.clearAll')"
type="transparent"
size="fit-content"
class="text-sm text-blue-500 dark-theme:text-blue-600"
class="dark-theme:text-blue-600 text-sm text-blue-500"
@click.stop="selectedItems = []"
/>
</div>
<div class="my-4 h-px bg-zinc-200 dark-theme:bg-zinc-700"></div>
<div class="dark-theme:bg-zinc-700 my-4 h-px bg-zinc-200"></div>
</div>
</template>
<!-- Trigger value (keep text scale identical) -->
<template #value>
<span class="text-sm text-zinc-700 dark-theme:text-gray-200">
<span class="dark-theme:text-smoke-200 text-sm text-zinc-700">
{{ label }}
</span>
<span
v-if="selectedCount > 0"
class="pointer-events-none absolute -top-2 -right-2 z-10 flex h-5 w-5 items-center justify-center rounded-full bg-blue-400 text-xs font-semibold text-white dark-theme:bg-blue-500"
class="dark-theme:bg-blue-500 pointer-events-none absolute -right-2 -top-2 z-10 flex size-5 items-center justify-center rounded-full bg-blue-400 text-xs font-semibold text-white"
>
{{ selectedCount }}
</span>
@@ -82,7 +82,7 @@
<template #option="slotProps">
<div class="flex items-center gap-2" :style="popoverStyle">
<div
class="flex h-4 w-4 shrink-0 items-center justify-center rounded p-0.5 transition-all duration-200"
class="flex size-4 shrink-0 items-center justify-center rounded p-0.5 transition-all duration-200"
:class="
slotProps.selected
? 'bg-blue-400 dark-theme:border-blue-500 dark-theme:bg-blue-500'

View File

@@ -101,12 +101,12 @@ export const KeyboardNavigationDemo: Story = {
},
template: `
<div class="space-y-6 p-4">
<div class="bg-blue-50 dark-theme:bg-blue-900/20 border border-blue-200 dark-theme:border-blue-700 rounded-lg p-4">
<div class="bg-blue-50 dark-theme:bg-blue-900/20 border border-azure-400 dark-theme:border-blue-700 rounded-lg p-4">
<h3 class="text-lg font-semibold mb-2">🎯 Keyboard Navigation Test</h3>
<p class="text-sm text-gray-600 dark-theme:text-gray-300 mb-4">
<p class="text-sm text-smoke-600 dark-theme:text-smoke-300 mb-4">
Use your keyboard to navigate these SingleSelect dropdowns:
</p>
<ol class="text-sm text-gray-600 dark-theme:text-gray-300 list-decimal list-inside space-y-1">
<ol class="text-sm text-smoke-600 dark-theme:text-smoke-300 list-decimal list-inside space-y-1">
<li><strong>Tab</strong> to focus the dropdown</li>
<li><strong>Enter/Space</strong> to open dropdown</li>
<li><strong>Arrow Up/Down</strong> to navigate options</li>
@@ -117,7 +117,7 @@ export const KeyboardNavigationDemo: Story = {
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200">
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200">
Sort Order
</label>
<SingleSelect
@@ -126,13 +126,13 @@ export const KeyboardNavigationDemo: Story = {
label="Choose sort order"
class="w-full"
/>
<p class="text-xs text-gray-500">
<p class="text-xs text-smoke-500">
Selected: {{ selectedSort ? sortOptions.find(o => o.value === selectedSort)?.name : 'None' }}
</p>
</div>
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200">
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200">
Task Priority (With Icon)
</label>
<SingleSelect
@@ -147,7 +147,7 @@ export const KeyboardNavigationDemo: Story = {
</svg>
</template>
</SingleSelect>
<p class="text-xs text-gray-500">
<p class="text-xs text-smoke-500">
Selected: {{ selectedPriority ? priorityOptions.find(o => o.value === selectedPriority)?.name : 'None' }}
</p>
</div>
@@ -191,10 +191,10 @@ export const ScreenReaderFriendly: Story = {
<div class="space-y-6 p-4">
<div class="bg-green-50 dark-theme:bg-green-900/20 border border-green-200 dark-theme:border-green-700 rounded-lg p-4">
<h3 class="text-lg font-semibold mb-2">♿ Screen Reader Test</h3>
<p class="text-sm text-gray-600 dark-theme:text-gray-300 mb-2">
<p class="text-sm text-smoke-600 dark-theme:text-smoke-300 mb-2">
These dropdowns have proper ARIA attributes and labels for screen readers:
</p>
<ul class="text-sm text-gray-600 dark-theme:text-gray-300 list-disc list-inside space-y-1">
<ul class="text-sm text-smoke-600 dark-theme:text-smoke-300 list-disc list-inside space-y-1">
<li><code>role="combobox"</code> identifies as dropdown</li>
<li><code>aria-haspopup="listbox"</code> indicates popup type</li>
<li><code>aria-expanded</code> shows open/closed state</li>
@@ -205,7 +205,7 @@ export const ScreenReaderFriendly: Story = {
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200" id="language-label">
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200" id="language-label">
Preferred Language
</label>
<SingleSelect
@@ -215,13 +215,13 @@ export const ScreenReaderFriendly: Story = {
class="w-full"
aria-labelledby="language-label"
/>
<p class="text-xs text-gray-500" aria-live="polite">
<p class="text-xs text-smoke-500" aria-live="polite">
Current: {{ selectedLanguage ? languageOptions.find(o => o.value === selectedLanguage)?.name : 'None selected' }}
</p>
</div>
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200" id="theme-label">
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200" id="theme-label">
Interface Theme
</label>
<SingleSelect
@@ -231,7 +231,7 @@ export const ScreenReaderFriendly: Story = {
class="w-full"
aria-labelledby="theme-label"
/>
<p class="text-xs text-gray-500" aria-live="polite">
<p class="text-xs text-smoke-500" aria-live="polite">
Current: {{ selectedTheme ? themeOptions.find(o => o.value === selectedTheme)?.name : 'No theme selected' }}
</p>
</div>
@@ -239,7 +239,7 @@ export const ScreenReaderFriendly: Story = {
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<h4 class="font-semibold mb-2">🎧 Screen Reader Testing Tips</h4>
<ul class="text-sm text-gray-600 dark-theme:text-gray-300 space-y-1">
<ul class="text-sm text-smoke-600 dark-theme:text-smoke-300 space-y-1">
<li>• Listen for role announcements when focusing</li>
<li>• Verify dropdown state changes are announced</li>
<li>• Check that selected values are spoken clearly</li>
@@ -299,7 +299,7 @@ export const FormIntegration: Story = {
<div class="max-w-2xl mx-auto p-6">
<div class="bg-purple-50 dark-theme:bg-purple-900/20 border border-purple-200 dark-theme:border-purple-700 rounded-lg p-4 mb-6">
<h3 class="text-lg font-semibold mb-2">📝 Form Integration Test</h3>
<p class="text-sm text-gray-600 dark-theme:text-gray-300">
<p class="text-sm text-smoke-600 dark-theme:text-smoke-300">
Test keyboard navigation through a complete form with SingleSelect components.
Tab order should be logical and all elements should be accessible.
</p>
@@ -307,19 +307,19 @@ export const FormIntegration: Story = {
<form @submit.prevent="handleSubmit" class="space-y-6">
<div>
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200 mb-1">
Title *
</label>
<input
type="text"
required
placeholder="Enter a title"
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="block w-full px-3 py-2 border border-smoke-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200 mb-1">
Category *
</label>
<SingleSelect
@@ -332,7 +332,7 @@ export const FormIntegration: Story = {
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200 mb-1">
Status
</label>
<SingleSelect
@@ -344,7 +344,7 @@ export const FormIntegration: Story = {
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200 mb-1">
Assignee
</label>
<SingleSelect
@@ -356,13 +356,13 @@ export const FormIntegration: Story = {
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200 mb-1">
Description
</label>
<textarea
rows="4"
placeholder="Enter description"
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="block w-full px-3 py-2 border border-smoke-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
@@ -375,16 +375,16 @@ export const FormIntegration: Story = {
</button>
<button
type="button"
class="px-4 py-2 bg-gray-300 dark-theme:bg-gray-600 text-gray-700 dark-theme:text-gray-200 rounded-md hover:bg-gray-400 dark-theme:hover:bg-gray-500 focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
class="px-4 py-2 bg-smoke-300 dark-theme:bg-smoke-600 text-smoke-700 dark-theme:text-smoke-200 rounded-md hover:bg-smoke-400 dark-theme:hover:bg-smoke-500 focus:ring-2 focus:ring-smoke-500 focus:ring-offset-2"
>
Cancel
</button>
</div>
</form>
<div class="mt-6 p-4 bg-gray-50 dark-theme:bg-zinc-800 border border-gray-200 dark-theme:border-zinc-700 rounded-lg">
<div class="mt-6 p-4 bg-gray-50 dark-theme:bg-zinc-800 border border-smoke-200 dark-theme:border-zinc-700 rounded-lg">
<h4 class="font-semibold mb-2">Current Form Data:</h4>
<pre class="text-xs text-gray-600 dark-theme:text-gray-300">{{ JSON.stringify(formData, null, 2) }}</pre>
<pre class="text-xs text-smoke-600 dark-theme:text-smoke-300">{{ JSON.stringify(formData, null, 2) }}</pre>
</div>
</div>
`
@@ -395,7 +395,7 @@ export const AccessibilityChecklist: Story = {
render: () => ({
template: `
<div class="max-w-4xl mx-auto p-6 space-y-6">
<div class="bg-gray-50 dark-theme:bg-zinc-800 border border-gray-200 dark-theme:border-zinc-700 rounded-lg p-6">
<div class="bg-gray-50 dark-theme:bg-zinc-800 border border-smoke-200 dark-theme:border-zinc-700 rounded-lg p-6">
<h2 class="text-2xl font-bold mb-4">♿ SingleSelect Accessibility Checklist</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@@ -442,9 +442,9 @@ export const AccessibilityChecklist: Story = {
</div>
</div>
<div class="mt-6 p-4 bg-blue-50 dark-theme:bg-blue-900/20 border border-blue-200 dark-theme:border-blue-700 rounded-lg">
<div class="mt-6 p-4 bg-blue-50 dark-theme:bg-blue-900/20 border border-azure-400 dark-theme:border-blue-700 rounded-lg">
<h4 class="font-semibold mb-2">🎯 Quick Test</h4>
<p class="text-sm text-gray-700 dark-theme:text-gray-200">
<p class="text-sm text-smoke-700 dark-theme:text-smoke-200">
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!
</p>
@@ -452,7 +452,7 @@ export const AccessibilityChecklist: Story = {
<div class="mt-4 p-4 bg-orange-50 border border-orange-200 rounded-lg">
<h4 class="font-semibold mb-2">⚡ Performance Note</h4>
<p class="text-sm text-gray-700 dark-theme:text-gray-200">
<p class="text-sm text-smoke-700 dark-theme:text-smoke-200">
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.
</p>

View File

@@ -26,11 +26,11 @@
<slot name="icon" />
<span
v-if="slotProps.value !== null && slotProps.value !== undefined"
class="text-zinc-700 dark-theme:text-gray-200"
class="dark-theme:text-smoke-200 text-zinc-700"
>
{{ getLabel(slotProps.value) }}
</span>
<span v-else class="text-zinc-700 dark-theme:text-gray-200">
<span v-else class="dark-theme:text-smoke-200 text-zinc-700">
{{ label }}
</span>
</div>
@@ -50,7 +50,7 @@
<span class="truncate">{{ option.name }}</span>
<i
v-if="selected"
class="icon-[lucide--check] text-neutral-600 dark-theme:text-white"
class="icon-[lucide--check] dark-theme:text-white text-neutral-600"
/>
</div>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div
class="relative h-full w-full"
class="relative size-full"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
@@ -59,7 +59,7 @@
/>
<div
v-if="enable3DViewer"
class="pointer-events-auto absolute top-12 right-2 z-20"
class="pointer-events-auto absolute right-2 top-12 z-20"
>
<ViewerControls :node="node" />
</div>

View File

@@ -1,6 +1,6 @@
<template>
<div
class="relative h-full w-full"
class="relative size-full"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
@@ -34,7 +34,7 @@
@up-direction-change="listenUpDirectionChange"
@recording-status-change="listenRecordingStatusChange"
/>
<div class="pointer-events-none absolute top-0 left-0 h-full w-full">
<div class="pointer-events-none absolute left-0 top-0 size-full">
<Load3DControls
:input-spec="inputSpec"
:background-color="backgroundColor"
@@ -69,7 +69,7 @@
</div>
<div
v-if="showRecordingControls"
class="pointer-events-auto absolute top-12 right-2 z-20"
class="pointer-events-auto absolute right-2 top-12 z-20"
>
<RecordingControls
:node="node"

View File

@@ -1,7 +1,7 @@
<template>
<div
v-if="animations && animations.length > 0"
class="pointer-events-auto absolute top-0 left-0 z-10 flex w-full items-center justify-center gap-2 pt-2"
class="pointer-events-auto absolute left-0 top-0 z-10 flex w-full items-center justify-center gap-2 pt-2"
>
<Button class="p-button-rounded p-button-text" @click="togglePlay">
<i

View File

@@ -1,6 +1,6 @@
<template>
<div
class="pointer-events-auto absolute top-12 left-2 z-20 flex flex-col rounded-lg bg-gray-700/30"
class="bg-smoke-700/30 pointer-events-auto absolute left-2 top-12 z-20 flex flex-col rounded-lg"
>
<div class="show-menu relative">
<Button class="p-button-rounded p-button-text" @click="toggleMenu">
@@ -9,14 +9,14 @@
<div
v-show="isMenuOpen"
class="absolute top-0 left-12 rounded-lg bg-black/50 shadow-lg"
class="absolute left-12 top-0 rounded-lg bg-black/50 shadow-lg"
>
<div class="flex flex-col">
<Button
v-for="category in availableCategories"
:key="category"
class="p-button-text flex w-full items-center justify-start"
:class="{ 'bg-gray-600': activeCategory === category }"
:class="{ 'bg-smoke-600': activeCategory === category }"
@click="selectCategory(category)"
>
<i :class="getCategoryIcon(category)" />
@@ -26,7 +26,7 @@
</div>
</div>
<div v-show="activeCategory" class="rounded-lg bg-gray-700/30">
<div v-show="activeCategory" class="bg-smoke-700/30 rounded-lg">
<SceneControls
v-if="activeCategory === 'scene'"
ref="sceneControlsRef"

View File

@@ -1,5 +1,5 @@
<template>
<div ref="container" class="comfy-load-3d relative h-full w-full">
<div ref="container" class="comfy-load-3d relative size-full">
<LoadingOverlay ref="loadingOverlayRef" />
</div>
</template>

View File

@@ -9,7 +9,7 @@
<div ref="mainContentRef" class="relative flex-1">
<div
ref="containerRef"
class="comfy-load-3d-viewer absolute h-full w-full"
class="comfy-load-3d-viewer absolute size-full"
@resize="viewer.handleResize"
/>
</div>

View File

@@ -18,7 +18,7 @@
</Button>
<div
v-show="showFOV"
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg"
class="absolute left-12 top-0 rounded-lg bg-black/50 p-4 shadow-lg"
style="width: 150px"
>
<Slider

View File

@@ -15,7 +15,7 @@
</Button>
<div
v-show="showExportFormats"
class="absolute top-0 left-12 rounded-lg bg-black/50 shadow-lg"
class="absolute left-12 top-0 rounded-lg bg-black/50 shadow-lg"
>
<div class="flex flex-col">
<Button

View File

@@ -15,7 +15,7 @@
</Button>
<div
v-show="showLightIntensity"
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg"
class="absolute left-12 top-0 rounded-lg bg-black/50 p-4 shadow-lg"
style="width: 150px"
>
<Slider

View File

@@ -12,7 +12,7 @@
</Button>
<div
v-show="showUpDirection"
class="absolute top-0 left-12 rounded-lg bg-black/50 shadow-lg"
class="absolute left-12 top-0 rounded-lg bg-black/50 shadow-lg"
>
<div class="flex flex-col">
<Button
@@ -43,7 +43,7 @@
</Button>
<div
v-show="showMaterialMode"
class="absolute top-0 left-12 rounded-lg bg-black/50 shadow-lg"
class="absolute left-12 top-0 rounded-lg bg-black/50 shadow-lg"
>
<div class="flex flex-col">
<Button
@@ -74,7 +74,7 @@
</Button>
<div
v-show="showEdgeThreshold"
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg"
class="absolute left-12 top-0 rounded-lg bg-black/50 p-4 shadow-lg"
style="width: 150px"
>
<label class="mb-1 block text-xs text-white"

View File

@@ -1,5 +1,5 @@
<template>
<div class="relative rounded-lg bg-gray-700/30">
<div class="bg-smoke-700/30 relative rounded-lg">
<div class="flex flex-col gap-2">
<Button
class="p-button-rounded p-button-text"

View File

@@ -24,7 +24,7 @@
ref="colorPickerRef"
type="color"
:value="backgroundColor"
class="pointer-events-none absolute m-0 h-0 w-0 p-0 opacity-0"
class="pointer-events-none absolute m-0 size-0 p-0 opacity-0"
@input="
updateBackgroundColor(($event.target as HTMLInputElement).value)
"
@@ -45,7 +45,7 @@
ref="imagePickerRef"
type="file"
accept="image/*"
class="pointer-events-none absolute m-0 h-0 w-0 p-0 opacity-0"
class="pointer-events-none absolute m-0 size-0 p-0 opacity-0"
@change="uploadBackgroundImage"
/>
</Button>

Some files were not shown because too many files have changed in this diff Show More