Compare commits
13 Commits
v1.30.4
...
fix/eslint
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92793177bd | ||
|
|
234fc3433c | ||
|
|
0a957fb2ac | ||
|
|
28a6089a94 | ||
|
|
298b3c629b | ||
|
|
20a1a9eda2 | ||
|
|
ca45b2c4d6 | ||
|
|
1453afad12 | ||
|
|
5de1a91f02 | ||
|
|
b3eee54abb | ||
|
|
1ee33673ab | ||
|
|
9f5245dc80 | ||
|
|
cacd7e3251 |
@@ -1,4 +1,4 @@
|
||||
name: size data
|
||||
name: "CI: Size Data"
|
||||
|
||||
on:
|
||||
push:
|
||||
128
.github/workflows/pr-backport.yaml
vendored
@@ -69,34 +69,7 @@ jobs:
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Check if backports already exist
|
||||
id: check-existing
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
|
||||
run: |
|
||||
# Check for existing backport PRs for this PR number
|
||||
EXISTING_BACKPORTS=$(gh pr list --state all --search "backport-${PR_NUMBER}-to" --json title,headRefName,baseRefName | jq -r '.[].headRefName')
|
||||
|
||||
if [ -z "$EXISTING_BACKPORTS" ]; then
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# For manual triggers with force_rerun, proceed anyway
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.force_rerun }}" = "true" ]; then
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
echo "::warning::Force rerun requested - existing backports will be updated"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Found existing backport PRs:"
|
||||
echo "$EXISTING_BACKPORTS"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
echo "::warning::Backport PRs already exist for PR #${PR_NUMBER}, skipping to avoid duplicates"
|
||||
|
||||
- name: Collect backport targets
|
||||
if: steps.check-existing.outputs.skip != 'true'
|
||||
id: targets
|
||||
run: |
|
||||
TARGETS=()
|
||||
@@ -138,6 +111,14 @@ jobs:
|
||||
add_target "$label" "${BASH_REMATCH[1]}"
|
||||
elif [[ "$label" =~ ^backport:(.+)$ ]]; then
|
||||
add_target "$label" "${BASH_REMATCH[1]}"
|
||||
elif [[ "$label" =~ ^core\/([0-9]+)\.([0-9]+)$ ]]; then
|
||||
SAFE_MAJOR="${BASH_REMATCH[1]}"
|
||||
SAFE_MINOR="${BASH_REMATCH[2]}"
|
||||
add_target "$label" "core/${SAFE_MAJOR}.${SAFE_MINOR}"
|
||||
elif [[ "$label" =~ ^cloud\/([0-9]+)\.([0-9]+)$ ]]; then
|
||||
SAFE_MAJOR="${BASH_REMATCH[1]}"
|
||||
SAFE_MINOR="${BASH_REMATCH[2]}"
|
||||
add_target "$label" "cloud/${SAFE_MAJOR}.${SAFE_MINOR}"
|
||||
elif [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||
add_target "$label" "core/${label}"
|
||||
fi
|
||||
@@ -151,8 +132,76 @@ jobs:
|
||||
echo "targets=${TARGETS[*]}" >> $GITHUB_OUTPUT
|
||||
echo "Found backport targets: ${TARGETS[*]}"
|
||||
|
||||
- name: Filter already backported targets
|
||||
id: filter-targets
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
FORCE_RERUN_INPUT: >-
|
||||
${{ github.event_name == 'workflow_dispatch' && inputs.force_rerun
|
||||
|| 'false' }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: >-
|
||||
${{ github.event_name == 'workflow_dispatch' && inputs.pr_number
|
||||
|| github.event.pull_request.number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
REQUESTED_TARGETS="${{ steps.targets.outputs.targets }}"
|
||||
if [ -z "$REQUESTED_TARGETS" ]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
echo "pending-targets=" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FORCE_RERUN=false
|
||||
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$FORCE_RERUN_INPUT" = "true" ]; then
|
||||
FORCE_RERUN=true
|
||||
fi
|
||||
|
||||
mapfile -t EXISTING_BRANCHES < <(
|
||||
git ls-remote --heads origin "backport-${PR_NUMBER}-to-*" || true
|
||||
)
|
||||
|
||||
PENDING=()
|
||||
SKIPPED=()
|
||||
|
||||
for target in $REQUESTED_TARGETS; do
|
||||
SAFE_TARGET=$(echo "$target" | tr '/' '-')
|
||||
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
|
||||
|
||||
if [ "$FORCE_RERUN" = true ]; then
|
||||
PENDING+=("$target")
|
||||
continue
|
||||
fi
|
||||
|
||||
if printf '%s\n' "${EXISTING_BRANCHES[@]:-}" |
|
||||
grep -Fq "refs/heads/${BACKPORT_BRANCH}"; then
|
||||
SKIPPED+=("$target")
|
||||
else
|
||||
PENDING+=("$target")
|
||||
fi
|
||||
done
|
||||
|
||||
SKIPPED_JOINED="${SKIPPED[*]:-}"
|
||||
PENDING_JOINED="${PENDING[*]:-}"
|
||||
|
||||
echo "already-exists=${SKIPPED_JOINED}" >> $GITHUB_OUTPUT
|
||||
echo "pending-targets=${PENDING_JOINED}" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ -z "$PENDING_JOINED" ]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
if [ -n "$SKIPPED_JOINED" ]; then
|
||||
echo "::warning::Backport branches already exist for: ${SKIPPED_JOINED}"
|
||||
fi
|
||||
else
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
if [ -n "$SKIPPED_JOINED" ]; then
|
||||
echo "::notice::Skipping already backported targets: ${SKIPPED_JOINED}"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Backport commits
|
||||
if: steps.check-existing.outputs.skip != 'true'
|
||||
if: steps.filter-targets.outputs.skip != 'true'
|
||||
id: backport
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
|
||||
@@ -170,7 +219,7 @@ jobs:
|
||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
||||
fi
|
||||
|
||||
for target in ${{ steps.targets.outputs.targets }}; do
|
||||
for target in ${{ steps.filter-targets.outputs.pending-targets }}; do
|
||||
TARGET_BRANCH="${target}"
|
||||
SAFE_TARGET=$(echo "$TARGET_BRANCH" | tr '/' '-')
|
||||
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
|
||||
@@ -185,6 +234,14 @@ jobs:
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if commit already exists on target branch
|
||||
if git branch -r --contains "${MERGE_COMMIT}" | grep -q "origin/${TARGET_BRANCH}"; then
|
||||
echo "::notice::Commit ${MERGE_COMMIT} already exists on ${TARGET_BRANCH}, skipping backport"
|
||||
FAILED="${FAILED}${TARGET_BRANCH}:already-exists "
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Create backport branch
|
||||
git checkout -b "${BACKPORT_BRANCH}" "origin/${TARGET_BRANCH}"
|
||||
|
||||
@@ -219,7 +276,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Create PR for each successful backport
|
||||
if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success
|
||||
if: steps.filter-targets.outputs.skip != 'true' && steps.backport.outputs.success
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
|
||||
@@ -258,7 +315,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Comment on failures
|
||||
if: steps.check-existing.outputs.skip != 'true' && failure() && steps.backport.outputs.failed
|
||||
if: steps.filter-targets.outputs.skip != 'true' && failure() && steps.backport.outputs.failed
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
@@ -279,6 +336,9 @@ jobs:
|
||||
if [ "${reason}" = "branch-missing" ]; then
|
||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport failed: Branch \`${target}\` does not exist"
|
||||
|
||||
elif [ "${reason}" = "already-exists" ]; then
|
||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Commit \`${MERGE_COMMIT}\` already exists on branch \`${target}\`. No backport needed."
|
||||
|
||||
elif [ "${reason}" = "conflicts" ]; then
|
||||
# Convert comma-separated conflicts back to newlines for display
|
||||
CONFLICTS_LIST=$(echo "${conflicts}" | tr ',' '\n' | sed 's/^/- /')
|
||||
@@ -287,3 +347,9 @@ jobs:
|
||||
gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Remove needs-backport label
|
||||
if: steps.filter-targets.outputs.skip != 'true' && success()
|
||||
run: gh pr edit ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} --remove-label "needs-backport"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
1
.github/workflows/pr-claude-review.yaml
vendored
@@ -28,6 +28,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
check-regexp: '^(lint-and-format|test|playwright-tests)'
|
||||
allowed-conclusions: success,skipped,failure,cancelled,neutral,action_required,timed_out,stale
|
||||
wait-interval: 30
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
name: size report
|
||||
name: "PR: Size Report"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['size data']
|
||||
workflows: ['CI: Size Data']
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
@@ -22,7 +22,7 @@ permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
size-report:
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
with:
|
||||
branch: ${{ steps.pr-base.outputs.content }}
|
||||
workflow: size-data.yml
|
||||
workflow: ci-size-data.yaml
|
||||
event: push
|
||||
name: size-data
|
||||
path: temp/size-prev
|
||||
118
.github/workflows/release-branch-create.yaml
vendored
@@ -69,6 +69,9 @@ jobs:
|
||||
echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "prev_minor=$PREV_MINOR" >> $GITHUB_OUTPUT
|
||||
|
||||
BASE_COMMIT=$(git rev-parse HEAD)
|
||||
echo "base_commit=$BASE_COMMIT" >> $GITHUB_OUTPUT
|
||||
|
||||
# Get previous major version for comparison
|
||||
PREV_MAJOR=$(echo $PREV_VERSION | cut -d. -f1)
|
||||
|
||||
@@ -87,13 +90,13 @@ jobs:
|
||||
elif [[ "$MAJOR" -gt "$PREV_MAJOR" && "$MINOR" == "0" && "$PATCH" == "0" ]]; then
|
||||
# Major version bump (e.g., 1.99.x → 2.0.0)
|
||||
echo "is_minor_bump=true" >> $GITHUB_OUTPUT
|
||||
BRANCH_NAME="core/${PREV_MAJOR}.${PREV_MINOR}"
|
||||
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
BRANCH_BASE="${PREV_MAJOR}.${PREV_MINOR}"
|
||||
echo "branch_base=$BRANCH_BASE" >> $GITHUB_OUTPUT
|
||||
elif [[ "$MAJOR" == "$PREV_MAJOR" && "$MINOR" -gt "$PREV_MINOR" && "$PATCH" == "0" ]]; then
|
||||
# Minor version bump (e.g., 1.23.x → 1.24.0)
|
||||
echo "is_minor_bump=true" >> $GITHUB_OUTPUT
|
||||
BRANCH_NAME="core/${MAJOR}.${PREV_MINOR}"
|
||||
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
BRANCH_BASE="${MAJOR}.${PREV_MINOR}"
|
||||
echo "branch_base=$BRANCH_BASE" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "is_minor_bump=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
@@ -101,64 +104,97 @@ jobs:
|
||||
# Return to main branch
|
||||
git checkout main
|
||||
|
||||
- name: Create release branch
|
||||
- name: Create release branches
|
||||
id: create_branches
|
||||
if: steps.check_version.outputs.is_minor_bump == 'true'
|
||||
run: |
|
||||
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}"
|
||||
BRANCH_BASE="${{ steps.check_version.outputs.branch_base }}"
|
||||
PREV_VERSION="${{ steps.check_version.outputs.prev_version }}"
|
||||
|
||||
# Check if branch already exists
|
||||
if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then
|
||||
echo "⚠️ Branch $BRANCH_NAME already exists, skipping creation"
|
||||
echo "branch_exists=true" >> $GITHUB_ENV
|
||||
exit 0
|
||||
else
|
||||
echo "branch_exists=false" >> $GITHUB_ENV
|
||||
if [[ -z "$BRANCH_BASE" ]]; then
|
||||
echo "::error::Branch base not set; unable to determine release branches"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create branch from the commit BEFORE the version bump
|
||||
# This ensures the release branch has the previous minor version
|
||||
git checkout -b "$BRANCH_NAME" HEAD^1
|
||||
BASE_COMMIT="${{ steps.check_version.outputs.base_commit }}"
|
||||
|
||||
# Push the new branch
|
||||
git push origin "$BRANCH_NAME"
|
||||
if [[ -z "$BASE_COMMIT" ]]; then
|
||||
echo "::error::Base commit not provided; cannot create release branches"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Created release branch: $BRANCH_NAME"
|
||||
echo "This branch is now in feature freeze and will only receive:"
|
||||
echo "- Bug fixes"
|
||||
echo "- Critical security patches"
|
||||
echo "- Documentation updates"
|
||||
RESULTS_FILE=$(mktemp)
|
||||
trap 'rm -f "$RESULTS_FILE"' EXIT
|
||||
|
||||
for PREFIX in core cloud; do
|
||||
BRANCH_NAME="${PREFIX}/${BRANCH_BASE}"
|
||||
|
||||
if git ls-remote --exit-code --heads origin \
|
||||
"$BRANCH_NAME" >/dev/null 2>&1; then
|
||||
echo "⚠️ Branch $BRANCH_NAME already exists"
|
||||
echo "ℹ️ Skipping creation for $BRANCH_NAME"
|
||||
STATUS="exists"
|
||||
else
|
||||
# Create branch from the commit BEFORE the version bump
|
||||
if ! git push origin "$BASE_COMMIT:refs/heads/$BRANCH_NAME"; then
|
||||
echo "::error::Failed to push release branch $BRANCH_NAME"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Created release branch: $BRANCH_NAME"
|
||||
STATUS="created"
|
||||
fi
|
||||
|
||||
echo "$BRANCH_NAME|$STATUS|$PREV_VERSION" >> "$RESULTS_FILE"
|
||||
done
|
||||
|
||||
{
|
||||
echo "results<<'EOF'"
|
||||
cat "$RESULTS_FILE"
|
||||
echo "EOF"
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Post summary
|
||||
if: steps.check_version.outputs.is_minor_bump == 'true'
|
||||
run: |
|
||||
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}"
|
||||
PREV_VERSION="${{ steps.check_version.outputs.prev_version }}"
|
||||
CURRENT_VERSION="${{ steps.check_version.outputs.current_version }}"
|
||||
RESULTS="${{ steps.create_branches.outputs.results }}"
|
||||
|
||||
if [[ "${{ env.branch_exists }}" == "true" ]]; then
|
||||
if [[ -z "$RESULTS" ]]; then
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
## 🌿 Release Branch Already Exists
|
||||
## 🌿 Release Branch Summary
|
||||
|
||||
The release branch for the previous minor version already exists:
|
||||
EOF
|
||||
else
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
## 🌿 Release Branch Created
|
||||
|
||||
A new release branch has been created for the previous minor version:
|
||||
Release branch creation skipped; no eligible branches were found.
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
## 🌿 Release Branch Summary
|
||||
|
||||
- **Branch**: \`$BRANCH_NAME\`
|
||||
- **Version**: \`$PREV_VERSION\` (feature frozen)
|
||||
- **Main branch**: \`$CURRENT_VERSION\` (active development)
|
||||
|
||||
### Branch Status
|
||||
EOF
|
||||
|
||||
while IFS='|' read -r BRANCH STATUS PREV_VERSION; do
|
||||
if [[ "$STATUS" == "created" ]]; then
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
|
||||
- \`$BRANCH\` created from version \`$PREV_VERSION\`
|
||||
EOF
|
||||
else
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
|
||||
- \`$BRANCH\` already existed (based on version \`$PREV_VERSION\`)
|
||||
EOF
|
||||
fi
|
||||
done <<< "$RESULTS"
|
||||
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
|
||||
### Branch Policy
|
||||
|
||||
The \`$BRANCH_NAME\` branch is now in **feature freeze** and will only accept:
|
||||
Release branches are feature-frozen and only accept:
|
||||
- 🐛 Bug fixes
|
||||
- 🔒 Security patches
|
||||
- 📚 Documentation updates
|
||||
@@ -167,9 +203,9 @@ jobs:
|
||||
|
||||
### Backporting Changes
|
||||
|
||||
To backport a fix to this release branch:
|
||||
To backport a fix:
|
||||
1. Create your fix on \`main\` first
|
||||
2. Cherry-pick to \`$BRANCH_NAME\`
|
||||
3. Create a PR targeting \`$BRANCH_NAME\`
|
||||
4. Use the \`Release\` label on the PR
|
||||
2. Cherry-pick to the target release branch
|
||||
3. Create a PR targeting that branch
|
||||
4. Apply the matching \`core/x.y\` or \`cloud/x.y\` label
|
||||
EOF
|
||||
|
||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
@@ -5,6 +5,9 @@ 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'
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types available for this plugin
|
||||
import tailwindV4Plugin from 'eslint-plugin-tailwind-v4'
|
||||
import unusedImports from 'eslint-plugin-unused-imports'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import { defineConfig } from 'eslint/config'
|
||||
@@ -33,7 +36,11 @@ const settings = {
|
||||
],
|
||||
noWarnOnMultipleProjects: true
|
||||
})
|
||||
]
|
||||
],
|
||||
tailwindcss: {
|
||||
config: `${import.meta.dirname}/packages/design-system/src/css/style.css`,
|
||||
functions: ['cn', 'clsx', 'tw']
|
||||
}
|
||||
} as const
|
||||
|
||||
const commonParserOptions = {
|
||||
@@ -100,7 +107,8 @@ export default defineConfig([
|
||||
plugins: {
|
||||
'unused-imports': unusedImports,
|
||||
// @ts-expect-error Bad types in the plugin
|
||||
'@intlify/vue-i18n': pluginI18n
|
||||
'@intlify/vue-i18n': pluginI18n,
|
||||
'tailwind-v4': tailwindV4Plugin
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
@@ -120,6 +128,7 @@ export default defineConfig([
|
||||
'import-x/no-relative-packages': 'error',
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'tailwind-v4/no-custom-classname': 'off', // TODO: fix
|
||||
'vue/no-v-html': 'off',
|
||||
// Enforce dark-theme: instead of dark: prefix
|
||||
'vue/no-restricted-class': ['error', '/^dark:/'],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.30.4",
|
||||
"version": "1.31.0",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -77,6 +77,7 @@
|
||||
"eslint-plugin-import-x": "catalog:",
|
||||
"eslint-plugin-prettier": "catalog:",
|
||||
"eslint-plugin-storybook": "catalog:",
|
||||
"eslint-plugin-tailwind-v4": "catalog:",
|
||||
"eslint-plugin-unused-imports": "catalog:",
|
||||
"eslint-plugin-vue": "catalog:",
|
||||
"fs-extra": "^11.2.0",
|
||||
|
||||
@@ -9,18 +9,29 @@
|
||||
|
||||
@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);
|
||||
|
||||
--text-xxxs: 0.5625rem;
|
||||
--text-xxxs--line-height: calc(1 / 0.5625);
|
||||
/* Spacing */
|
||||
--spacing-xs: 8px;
|
||||
|
||||
/* Font Families */
|
||||
--font-inter: 'Inter', sans-serif;
|
||||
|
||||
/* Palette Colors */
|
||||
--color-charcoal-100: #55565e;
|
||||
--color-charcoal-100: #171718;
|
||||
--color-charcoal-200: #494a50;
|
||||
--color-charcoal-300: #3c3d42;
|
||||
--color-charcoal-400: #313235;
|
||||
@@ -31,60 +42,43 @@
|
||||
|
||||
--color-neutral-550: #636363;
|
||||
|
||||
--color-ash-300: #bbbbbb;
|
||||
--color-ash-500: #828282;
|
||||
--color-ash-800: #444444;
|
||||
--color-stone-100: #828282;
|
||||
--color-stone-200: #444444;
|
||||
--color-stone-300: #bbbbbb;
|
||||
|
||||
--color-ivory-100: #fdfbfa;
|
||||
--color-ivory-200: #faf9f5;
|
||||
--color-ivory-300: #f0eee6;
|
||||
|
||||
--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-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-sand-100: #e1ded5;
|
||||
--color-sand-200: #fff7d5;
|
||||
--color-sand-200: #d6cfc2;
|
||||
--color-sand-300: #888682;
|
||||
--color-sand-400: #eed7ac;
|
||||
|
||||
--color-pure-black: #000000;
|
||||
--color-pure-white: #ffffff;
|
||||
|
||||
--color-slate-100: #9c9eab;
|
||||
--color-slate-200: #9fa2bd;
|
||||
--color-slate-300: #5b5e7d;
|
||||
|
||||
--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-brand-yellow: #f0ff41;
|
||||
--color-brand-blue: #172dd7;
|
||||
|
||||
--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;
|
||||
|
||||
@@ -96,31 +90,28 @@
|
||||
--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-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-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-alpha-charcoal-600-30: color-mix(
|
||||
in srgb,
|
||||
var(--color-charcoal-600) 30%,
|
||||
transparent
|
||||
);
|
||||
--color-alpha-ash-500-20: color-mix(
|
||||
--color-alpha-stone-100-20: color-mix(
|
||||
in srgb,
|
||||
var(--color-ash-500) 20%,
|
||||
var(--color-stone-100) 20%,
|
||||
transparent
|
||||
);
|
||||
--color-alpha-smoke-500-50: color-mix(
|
||||
--color-alpha-gray-500-50: color-mix(
|
||||
in srgb,
|
||||
var(--color-smoke-500) 50%,
|
||||
var(--color-gray-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);
|
||||
@@ -154,8 +145,8 @@
|
||||
--content-hover-bg: #adadad;
|
||||
--content-hover-fg: #000;
|
||||
|
||||
--button-surface: var(--color-white);
|
||||
--button-surface-contrast: var(--color-black);
|
||||
--button-surface: var(--color-pure-white);
|
||||
--button-surface-contrast: var(--color-pure-black);
|
||||
|
||||
/* Code styling colors for help menu*/
|
||||
--code-text-color: rgb(0 122 255 / 1);
|
||||
@@ -166,36 +157,31 @@
|
||||
|
||||
--accent-primary: var(--color-charcoal-700);
|
||||
--backdrop: var(--color-white);
|
||||
|
||||
--button-hover-surface: var(--color-smoke-200);
|
||||
--button-active-surface: var(--color-smoke-400);
|
||||
--button-icon: var(--color-smoke-600);
|
||||
|
||||
--button-hover-surface: var(--color-gray-200);
|
||||
--button-active-surface: var(--color-gray-400);
|
||||
--button-icon: var(--color-gray-600);
|
||||
--dialog-surface: var(--color-neutral-200);
|
||||
|
||||
--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);
|
||||
--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);
|
||||
--node-component-executing: var(--color-blue-500);
|
||||
--node-component-header: var(--fg-color);
|
||||
--node-component-header-icon: var(--color-ash-800);
|
||||
--node-component-header-icon: var(--color-stone-200);
|
||||
--node-component-header-surface: var(--color-white);
|
||||
--node-component-outline: var(--color-black);
|
||||
--node-component-ring: rgb(from var(--color-smoke-500) r g b / 50%);
|
||||
--node-component-ring: rgb(from var(--color-gray-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-ash-800);
|
||||
--node-component-surface-highlight: var(--color-ash-500);
|
||||
--node-component-surface-hovered: var(--color-smoke-200);
|
||||
--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-surface-selected: var(--color-charcoal-200);
|
||||
--node-component-surface: var(--color-white);
|
||||
--node-component-tooltip: var(--color-charcoal-700);
|
||||
@@ -207,86 +193,40 @@
|
||||
);
|
||||
--node-component-widget-skeleton-surface: var(--color-zinc-300);
|
||||
--node-divider: var(--color-sand-100);
|
||||
--node-icon-disabled: var(--color-alpha-smoke-500-50);
|
||||
--node-stroke: var(--color-smoke-400);
|
||||
--node-icon-disabled: var(--color-alpha-gray-500-50);
|
||||
--node-stroke: var(--color-gray-400);
|
||||
--node-stroke-selected: var(--color-accent-primary);
|
||||
--node-stroke-error: var(--color-error);
|
||||
--node-stroke-executing: var(--color-azure-600);
|
||||
|
||||
--text-secondary: var(--color-ash-500);
|
||||
--node-stroke-executing: var(--color-blue-100);
|
||||
--text-secondary: var(--color-stone-100);
|
||||
--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 {
|
||||
--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);
|
||||
--accent-primary: var(--color-pure-white);
|
||||
--backdrop: var(--color-neutral-900);
|
||||
|
||||
--button-surface: var(--color-charcoal-600);
|
||||
--button-surface-contrast: var(--color-white);
|
||||
--button-surface-contrast: var(--color-pure-white);
|
||||
--button-hover-surface: var(--color-charcoal-600);
|
||||
--button-active-surface: var(--color-charcoal-600);
|
||||
--button-icon: var(--color-smoke-800);
|
||||
|
||||
--button-icon: var(--color-gray-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-800);
|
||||
--interface-panel-surface: var(--color-charcoal-100);
|
||||
--interface-stroke: var(--color-charcoal-400);
|
||||
|
||||
--nav-background: var(--color-charcoal-800);
|
||||
|
||||
--nav-background: var(--color-charcoal-100);
|
||||
--node-border: var(--color-charcoal-500);
|
||||
--node-component-border: var(--color-ash-800);
|
||||
--node-component-border: var(--color-stone-200);
|
||||
--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-smoke-500) / 20%);
|
||||
--node-component-ring: rgb(var(--color-gray-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);
|
||||
@@ -300,37 +240,14 @@
|
||||
--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-ash-500-20);
|
||||
--node-stroke: var(--color-ash-800);
|
||||
--node-stroke-selected: var(--color-white);
|
||||
--node-icon-disabled: var(--color-alpha-stone-100-20);
|
||||
--node-stroke: var(--color-stone-200);
|
||||
--node-stroke-selected: var(--color-pure-white);
|
||||
--node-stroke-error: var(--color-error);
|
||||
--node-stroke-executing: var(--color-azure-600);
|
||||
|
||||
--node-stroke-executing: var(--color-blue-100);
|
||||
--text-secondary: var(--color-slate-100);
|
||||
--text-primary: var(--color-white);
|
||||
|
||||
--text-primary: var(--color-pure-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 {
|
||||
@@ -351,14 +268,6 @@
|
||||
--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);
|
||||
@@ -403,27 +312,6 @@
|
||||
--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 {
|
||||
@@ -442,6 +330,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===================== Scrollbar Utilities (Tailwind) =====================
|
||||
Usage: Add `scrollbar-custom` class to scrollable containers.
|
||||
The scrollbar styling adapts to light/dark theme automatically.
|
||||
|
||||
@@ -4,13 +4,6 @@ import { addDynamicIconSelectors } from '@iconify/tailwind'
|
||||
import { iconCollection } from './src/iconCollection'
|
||||
|
||||
export default {
|
||||
theme: {
|
||||
extend: {
|
||||
boxShadow: {
|
||||
interface: 'var(--interface-panel-box-shadow)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
addDynamicIconSelectors({
|
||||
iconSets: {
|
||||
|
||||
@@ -474,13 +474,3 @@ 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
|
||||
}
|
||||
|
||||
@@ -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-smoke-500')"
|
||||
:class="cn('px-4 py-2', isActive ? 'bg-blue-500' : 'bg-gray-500')"
|
||||
/>
|
||||
```
|
||||
|
||||
|
||||
24
pnpm-lock.yaml
generated
@@ -150,6 +150,9 @@ catalogs:
|
||||
eslint-plugin-storybook:
|
||||
specifier: ^9.1.6
|
||||
version: 9.1.6
|
||||
eslint-plugin-tailwind-v4:
|
||||
specifier: ^1.0.13
|
||||
version: 1.0.13
|
||||
eslint-plugin-unused-imports:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@@ -561,6 +564,9 @@ 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-tailwind-v4:
|
||||
specifier: 'catalog:'
|
||||
version: 1.0.13(eslint@9.35.0(jiti@2.4.2))
|
||||
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))
|
||||
@@ -4685,6 +4691,12 @@ packages:
|
||||
eslint: '>=8'
|
||||
storybook: ^9.1.6
|
||||
|
||||
eslint-plugin-tailwind-v4@1.0.13:
|
||||
resolution: {integrity: sha512-IXCv/Miug48/mXaipqdxypsozGvbgLz/8C0ZMCf0WFW1FBXJz4xPaRJFjbsY9vu1Vbf4+xfyLqz8inbQyc6MZw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
eslint: '>=8.0.0'
|
||||
|
||||
eslint-plugin-unused-imports@4.2.0:
|
||||
resolution: {integrity: sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==}
|
||||
peerDependencies:
|
||||
@@ -7600,8 +7612,8 @@ 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-component-type-helpers@3.1.2:
|
||||
resolution: {integrity: sha512-ch3/SKBtxdZq18vsEntiGCdSszCRNfhX5QaTxjSacCAXLlNQRXfXo+ANjoQEYJMsJOJy1/vHF6Tkc4s85MS+zw==}
|
||||
|
||||
vue-demi@0.14.10:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
@@ -10285,7 +10297,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.3
|
||||
vue-component-type-helpers: 3.1.2
|
||||
|
||||
'@swc/helpers@0.5.17':
|
||||
dependencies:
|
||||
@@ -12355,6 +12367,10 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
eslint-plugin-tailwind-v4@1.0.13(eslint@9.35.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
eslint: 9.35.0(jiti@2.4.2)
|
||||
|
||||
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)
|
||||
@@ -15943,7 +15959,7 @@ snapshots:
|
||||
|
||||
vue-component-type-helpers@3.1.1: {}
|
||||
|
||||
vue-component-type-helpers@3.1.3: {}
|
||||
vue-component-type-helpers@3.1.2: {}
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
|
||||
dependencies:
|
||||
|
||||
@@ -51,6 +51,7 @@ catalog:
|
||||
eslint-plugin-import-x: ^4.16.1
|
||||
eslint-plugin-prettier: ^5.5.4
|
||||
eslint-plugin-storybook: ^9.1.6
|
||||
eslint-plugin-tailwind-v4: ^1.0.13
|
||||
eslint-plugin-unused-imports: ^4.2.0
|
||||
eslint-plugin-vue: ^10.4.0
|
||||
firebase: ^11.6.0
|
||||
@@ -62,7 +63,6 @@ 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
|
||||
@@ -98,6 +98,7 @@ 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
|
||||
|
||||
|
||||
@@ -314,6 +314,11 @@ function renderCategoryDetails(report) {
|
||||
|
||||
for (const category of report.categories) {
|
||||
lines.push(renderCategoryBlock(category, report.hasBaseline))
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
if (report.categories.length > 0) {
|
||||
lines.pop()
|
||||
}
|
||||
|
||||
lines.push('</details>')
|
||||
@@ -339,9 +344,11 @@ function renderCategoryBlock(category, hasBaseline) {
|
||||
|
||||
summaryParts.push('</summary>')
|
||||
lines.push(summaryParts.join(''))
|
||||
lines.push('')
|
||||
|
||||
if (category.description) {
|
||||
lines.push(`_${category.description}_`)
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
if (category.bundles.length === 0) {
|
||||
@@ -382,6 +389,7 @@ function renderCategoryBlock(category, hasBaseline) {
|
||||
})
|
||||
|
||||
lines.push(markdownTable([headers, ...rows]))
|
||||
lines.push('')
|
||||
|
||||
const statusParts = []
|
||||
if (category.counts.added) statusParts.push(`${category.counts.added} added`)
|
||||
@@ -393,10 +401,11 @@ function renderCategoryBlock(category, hasBaseline) {
|
||||
statusParts.push(`${category.counts.decreased} shrank`)
|
||||
|
||||
if (statusParts.length > 0) {
|
||||
lines.push(`\n_Status:_ ${statusParts.join(' / ')}`)
|
||||
lines.push(`_Status:_ ${statusParts.join(' / ')}`)
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
lines.push('</details>\n')
|
||||
lines.push('</details>')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
|
||||
@@ -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": "#141414",
|
||||
"CLEAR_BACKGROUND_COLOR": "#222",
|
||||
"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": "#171718",
|
||||
"comfy-menu-bg": "#353535",
|
||||
"comfy-menu-secondary-bg": "#303030",
|
||||
"comfy-input-bg": "#222",
|
||||
"input-text": "#ddd",
|
||||
|
||||
@@ -68,12 +68,7 @@
|
||||
"content-fg": "#222",
|
||||
"content-hover-bg": "#adadad",
|
||||
"content-hover-fg": "#222",
|
||||
"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"
|
||||
"bar-shadow": "rgba(16, 16, 16, 0.25) 0 0 0.5rem"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<slot name="topmenu" :sidebar-panel-visible="sidebarPanelVisible" />
|
||||
|
||||
<Splitter
|
||||
class="splitter-overlay splitter-overlay-bottom mx-1 mb-1 flex-1"
|
||||
class="splitter-overlay splitter-overlay-bottom mr-1 mb-1 ml-1 flex-1"
|
||||
layout="vertical"
|
||||
:pt:gutter="
|
||||
'rounded-tl-lg rounded-tr-lg ' +
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-show="workspaceState.focusMode"
|
||||
class="comfy-menu-hamburger no-drag right-0 top-0"
|
||||
class="comfy-menu-hamburger no-drag top-0 right-0"
|
||||
>
|
||||
<Button
|
||||
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div v-if="!workspaceStore.focusMode" class="ml-1 flex gap-x-0.5 pt-1">
|
||||
<div v-if="!workspaceStore.focusMode" class="ml-2 flex pt-1">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SubgraphBreadcrumb />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="actionbar-container pointer-events-auto flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] px-2 shadow-interface"
|
||||
class="actionbar-container pointer-events-auto mx-1 flex h-12 items-center rounded-lg px-2 shadow-md"
|
||||
>
|
||||
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
|
||||
<div
|
||||
@@ -48,5 +48,6 @@ onMounted(() => {
|
||||
<style scoped>
|
||||
.actionbar-container {
|
||||
background-color: var(--comfy-menu-bg);
|
||||
border: 1px solid var(--p-panel-border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
|
||||
<Panel
|
||||
class="z-1000 pointer-events-auto"
|
||||
class="pointer-events-auto z-1000"
|
||||
:style="style"
|
||||
:class="panelClass"
|
||||
:pt="{
|
||||
@@ -18,7 +18,7 @@
|
||||
content: { class: isDocked ? 'p-0' : 'p-1' }
|
||||
}"
|
||||
>
|
||||
<div ref="panelRef" class="flex select-none items-center">
|
||||
<div ref="panelRef" class="flex items-center select-none">
|
||||
<span
|
||||
ref="dragHandleRef"
|
||||
:class="
|
||||
@@ -258,9 +258,7 @@ 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 shadow-interface'
|
||||
isDocked.value ? 'p-0 static mr-2 border-none bg-transparent' : 'fixed'
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class="flex flex-col"
|
||||
>
|
||||
<h3
|
||||
class="subcategory-title text-surface-600 dark-theme:text-surface-400 mb-4 text-xs font-bold uppercase tracking-wide"
|
||||
class="subcategory-title mb-4 text-xs font-bold tracking-wide text-surface-600 uppercase dark-theme:text-surface-400"
|
||||
>
|
||||
{{ getSubcategoryTitle(subcategory) }}
|
||||
</h3>
|
||||
@@ -16,7 +16,7 @@
|
||||
<div
|
||||
v-for="command in subcategoryCommands"
|
||||
:key="command.id"
|
||||
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"
|
||||
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"
|
||||
>
|
||||
<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 bg-surface-200 dark-theme:bg-surface-600 min-w-6 rounded border px-2 py-1 text-center font-mono text-xs"
|
||||
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"
|
||||
>
|
||||
{{ formatKey(key) }}
|
||||
</span>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div ref="rootEl" class="relative size-full overflow-hidden bg-neutral-900">
|
||||
<div class="p-terminal size-full rounded-none p-2">
|
||||
<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="terminalEl" class="terminal-host h-full" />
|
||||
</div>
|
||||
<Button
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="size-full bg-transparent">
|
||||
<div class="h-full w-full bg-transparent">
|
||||
<p v-if="errorMessage" class="p-4 text-center">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="subgraph-breadcrumb w-auto drop-shadow-[var(--interface-panel-drop-shadow)]"
|
||||
class="subgraph-breadcrumb w-auto drop-shadow-md"
|
||||
: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(--interface-stroke);
|
||||
border-bottom: 1px solid var(--interface-stroke);
|
||||
border-top: 1px solid var(--p-panel-border-color);
|
||||
border-bottom: 1px solid var(--p-panel-border-color);
|
||||
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(--interface-stroke);
|
||||
border-left: 1px solid var(--p-panel-border-color);
|
||||
|
||||
.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(--interface-stroke);
|
||||
border-right: 1px solid var(--p-panel-border-color);
|
||||
}
|
||||
|
||||
:deep(.p-breadcrumb-item-link:hover),
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
v-if="isEditing"
|
||||
ref="itemInputRef"
|
||||
v-model="itemLabel"
|
||||
class="z-10000 fixed p-2 text-[.8rem]"
|
||||
class="fixed z-10000 px-2 py-2 text-[.8rem]"
|
||||
@blur="inputBlur(false)"
|
||||
@click.stop
|
||||
@keydown.enter="inputBlur(true)"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="dark-theme:text-zinc-400 line-clamp-2 h-7 text-xs text-zinc-500">
|
||||
<div class="line-clamp-2 h-7 text-xs text-zinc-500 dark-theme:text-zinc-400">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="topStyle">
|
||||
<slot class="absolute left-0 top-0 size-full"></slot>
|
||||
<slot class="absolute top-0 left-0 h-full w-full"></slot>
|
||||
|
||||
<div v-if="slots['top-left']" :class="slotClasses['top-left']">
|
||||
<slot name="top-left"></slot>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
<div class="image-upload-wrapper">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="preview-box flex size-16 items-center justify-center rounded border p-2"
|
||||
:class="{ 'bg-smoke-100 dark-theme:bg-smoke-800': !modelValue }"
|
||||
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 }"
|
||||
>
|
||||
<img
|
||||
v-if="modelValue"
|
||||
:src="modelValue"
|
||||
class="max-h-full max-w-full object-contain"
|
||||
/>
|
||||
<i v-else class="pi pi-image text-smoke-400 text-xl" />
|
||||
<i v-else class="pi pi-image text-xl text-gray-400" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="relative flex size-full items-center justify-center overflow-hidden"
|
||||
class="relative flex h-full w-full items-center justify-center overflow-hidden"
|
||||
:class="containerClass"
|
||||
>
|
||||
<Skeleton
|
||||
@@ -23,7 +23,7 @@
|
||||
/>
|
||||
<div
|
||||
v-if="hasError"
|
||||
class="bg-surface-50 text-muted dark-theme:bg-surface-800 absolute inset-0 flex items-center justify-center"
|
||||
class="absolute inset-0 flex items-center justify-center bg-surface-50 text-muted dark-theme:bg-surface-800"
|
||||
>
|
||||
<img
|
||||
src="/assets/images/default-template.png"
|
||||
|
||||
@@ -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="whitespace-pre-line text-center">
|
||||
<p :class="textClass" class="text-center whitespace-pre-line">
|
||||
{{ message }}
|
||||
</p>
|
||||
<Button
|
||||
|
||||
@@ -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('icon-[lucide--user]')
|
||||
expect(avatar.props('icon')).toBe('pi pi-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('icon-[lucide--user]')
|
||||
expect(avatar.props('icon')).toBe('pi pi-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('icon-[lucide--user]')
|
||||
expect(avatar.props('icon')).toBe('pi pi-user')
|
||||
})
|
||||
|
||||
it('uses provided ariaLabel', () => {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<Avatar
|
||||
class="bg-interface-panel-selected-surface"
|
||||
:image="photoUrl ?? undefined"
|
||||
:icon="hasAvatar ? undefined : 'icon-[lucide--user]'"
|
||||
:pt:icon:class="{ 'size-4': !hasAvatar }"
|
||||
:icon="hasAvatar ? undefined : 'pi pi-user'"
|
||||
shape="circle"
|
||||
:aria-label="ariaLabel ?? $t('auth.login.userAvatar')"
|
||||
@error="handleImageError"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</template>
|
||||
|
||||
<template #contentFilter>
|
||||
<div class="relative flex flex-wrap gap-2 px-6 pb-4 pt-2">
|
||||
<div class="relative flex flex-wrap gap-2 px-6 pt-2 pb-4">
|
||||
<!-- Model Filter -->
|
||||
<MultiSelect
|
||||
v-model="selectedModelObjects"
|
||||
@@ -97,7 +97,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="!isLoading"
|
||||
class="text-neutral px-6 pb-2 pt-4 text-2xl font-semibold"
|
||||
class="text-neutral px-6 pt-4 pb-2 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="icon-[lucide--search] mb-4 size-12 opacity-50" />
|
||||
<i class="mb-4 icon-[lucide--search] h-12 w-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="bg-dialog-surface inline-block h-8 w-48 animate-pulse rounded"
|
||||
class="inline-block h-8 w-48 animate-pulse rounded bg-dialog-surface"
|
||||
></span>
|
||||
|
||||
<!-- Template Cards Grid -->
|
||||
@@ -144,12 +144,14 @@
|
||||
size="compact"
|
||||
variant="ghost"
|
||||
rounded="lg"
|
||||
class="hover:bg-base-background"
|
||||
class="hover:bg-white dark-theme:hover:bg-zinc-800"
|
||||
>
|
||||
<template #top>
|
||||
<CardTop ratio="landscape">
|
||||
<template #default>
|
||||
<div class="bg-dialog-surface size-full animate-pulse"></div>
|
||||
<div
|
||||
class="h-full w-full animate-pulse bg-dialog-surface"
|
||||
></div>
|
||||
</template>
|
||||
</CardTop>
|
||||
</template>
|
||||
@@ -157,10 +159,10 @@
|
||||
<CardBottom>
|
||||
<div class="px-4 py-3">
|
||||
<div
|
||||
class="bg-dialog-surface mb-2 h-6 animate-pulse rounded"
|
||||
class="mb-2 h-6 animate-pulse rounded bg-dialog-surface"
|
||||
></div>
|
||||
<div
|
||||
class="bg-dialog-surface h-4 animate-pulse rounded"
|
||||
class="h-4 animate-pulse rounded bg-dialog-surface"
|
||||
></div>
|
||||
</div>
|
||||
</CardBottom>
|
||||
@@ -176,7 +178,7 @@
|
||||
variant="ghost"
|
||||
rounded="lg"
|
||||
:data-testid="`template-workflow-${template.name}`"
|
||||
class="hover:bg-base-background"
|
||||
class="hover:bg-white dark-theme:hover:bg-zinc-800"
|
||||
@mouseenter="hoveredTemplate = template.name"
|
||||
@mouseleave="hoveredTemplate = null"
|
||||
@click="onLoadWorkflow(template)"
|
||||
@@ -185,7 +187,9 @@
|
||||
<CardTop ratio="square">
|
||||
<template #default>
|
||||
<!-- Template Thumbnail -->
|
||||
<div class="relative size-full overflow-hidden rounded-lg">
|
||||
<div
|
||||
class="relative h-full w-full overflow-hidden rounded-lg"
|
||||
>
|
||||
<template v-if="template.mediaType === 'audio'">
|
||||
<AudioThumbnail :src="getBaseThumbnailSrc(template)" />
|
||||
</template>
|
||||
@@ -248,7 +252,7 @@
|
||||
</template>
|
||||
<ProgressSpinner
|
||||
v-if="loadingTemplate === template.name"
|
||||
class="absolute inset-0 z-10 m-auto size-12"
|
||||
class="absolute inset-0 z-10 m-auto h-12 w-12"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -285,7 +289,7 @@
|
||||
<div class="flex justify-between gap-2">
|
||||
<div class="flex-1">
|
||||
<p
|
||||
class="text-muted m-0 line-clamp-2 text-sm"
|
||||
class="m-0 line-clamp-2 text-sm text-muted"
|
||||
:title="getTemplateDescription(template)"
|
||||
>
|
||||
{{ getTemplateDescription(template) }}
|
||||
@@ -319,12 +323,14 @@
|
||||
size="compact"
|
||||
variant="ghost"
|
||||
rounded="lg"
|
||||
class="hover:bg-base-background"
|
||||
class="hover:bg-white dark-theme:hover:bg-zinc-800"
|
||||
>
|
||||
<template #top>
|
||||
<CardTop ratio="square">
|
||||
<template #default>
|
||||
<div class="bg-dialog-surface size-full animate-pulse"></div>
|
||||
<div
|
||||
class="h-full w-full animate-pulse bg-dialog-surface"
|
||||
></div>
|
||||
</template>
|
||||
</CardTop>
|
||||
</template>
|
||||
@@ -332,10 +338,10 @@
|
||||
<CardBottom>
|
||||
<div class="px-4 py-3">
|
||||
<div
|
||||
class="bg-dialog-surface mb-2 h-6 animate-pulse rounded"
|
||||
class="mb-2 h-6 animate-pulse rounded bg-dialog-surface"
|
||||
></div>
|
||||
<div
|
||||
class="bg-dialog-surface h-4 animate-pulse rounded"
|
||||
class="h-4 animate-pulse rounded bg-dialog-surface"
|
||||
></div>
|
||||
</div>
|
||||
</CardBottom>
|
||||
@@ -350,7 +356,7 @@
|
||||
ref="loadTrigger"
|
||||
class="mt-4 flex h-4 w-full items-center justify-center"
|
||||
>
|
||||
<div v-if="isLoadingMore" class="text-muted text-sm">
|
||||
<div v-if="isLoadingMore" class="text-sm text-muted">
|
||||
{{ $t('templateWorkflows.loadingMore', 'Loading more...') }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -358,7 +364,7 @@
|
||||
<!-- Results Summary -->
|
||||
<div
|
||||
v-if="!isLoading"
|
||||
class="dark-theme:text-neutral-400 mt-6 px-6 text-sm text-neutral-600"
|
||||
class="mt-6 px-6 text-sm text-neutral-600 dark-theme:text-neutral-400"
|
||||
>
|
||||
{{
|
||||
$t('templateWorkflows.resultsCount', {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="h-110 flex max-w-96 flex-col gap-4 p-2">
|
||||
<div class="flex h-110 max-w-96 flex-col gap-4 p-2">
|
||||
<div class="mb-2 text-2xl font-medium">
|
||||
{{ t('apiNodesSignInDialog.title') }}
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<template v-if="reportOpen">
|
||||
<Divider />
|
||||
<ScrollPanel class="h-[400px] w-full max-w-[80vw]">
|
||||
<pre class="whitespace-pre-wrap break-words">{{ reportContent }}</pre>
|
||||
<pre class="break-words whitespace-pre-wrap">{{ reportContent }}</pre>
|
||||
</ScrollPanel>
|
||||
<Divider />
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="hasMissingCoreNodes"
|
||||
severity="info"
|
||||
icon="pi pi-info-circle"
|
||||
class="m-2"
|
||||
class="mx-2 my-2"
|
||||
:pt="{
|
||||
root: { class: 'flex-col' },
|
||||
text: { class: 'flex-1' }
|
||||
@@ -25,7 +25,7 @@
|
||||
class="ml-4"
|
||||
>
|
||||
<div
|
||||
class="text-surface-600 dark-theme:text-surface-400 text-sm font-medium"
|
||||
class="text-sm font-medium text-surface-600 dark-theme:text-surface-400"
|
||||
>
|
||||
{{
|
||||
$t('loadWorkflowWarning.coreNodesFromVersion', {
|
||||
@@ -33,7 +33,7 @@
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<div class="text-surface-500 dark-theme:text-surface-500 ml-4 text-sm">
|
||||
<div class="ml-4 text-sm text-surface-500 dark-theme:text-surface-500">
|
||||
{{ getUniqueNodeNames(nodes).join(', ') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<template v-else>
|
||||
<!-- Header -->
|
||||
<div class="mb-8 flex flex-col gap-4">
|
||||
<h1 class="my-0 text-2xl font-medium leading-normal">
|
||||
<h1 class="my-0 text-2xl leading-normal font-medium">
|
||||
{{ 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 size-5"
|
||||
class="mr-2 h-5 w-5"
|
||||
:alt="$t('g.comfy')"
|
||||
/>
|
||||
{{ t('auth.login.useApiKey') }}
|
||||
</Button>
|
||||
<small class="text-muted text-center">
|
||||
<small class="text-center text-muted">
|
||||
{{ t('auth.apiKey.helpText') }}
|
||||
<a
|
||||
:href="`${COMFY_PLATFORM_BASE_URL}/login`"
|
||||
@@ -115,7 +115,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Terms & Contact -->
|
||||
<p class="text-muted mt-8 text-xs">
|
||||
<p class="mt-8 text-xs text-muted">
|
||||
{{ t('auth.login.termsText') }}
|
||||
<a
|
||||
href="https://www.comfy.org/terms-of-service"
|
||||
|
||||
@@ -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 font-medium leading-normal">
|
||||
<h1 class="my-0 text-2xl leading-normal font-medium">
|
||||
{{ $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-muted text-base">
|
||||
<div class="text-base text-muted">
|
||||
{{ $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-muted text-sm"
|
||||
<span class="text-sm text-muted"
|
||||
>{{ $t('credits.topUp.quickPurchase') }}:</span
|
||||
>
|
||||
<div class="grid grid-cols-[2fr_1fr] gap-2">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
/>
|
||||
<span v-else class="text-xl">{{ amount }}</span>
|
||||
</div>
|
||||
<ProgressSpinner v-if="loading" class="size-8" />
|
||||
<ProgressSpinner v-if="loading" class="h-8 w-8" />
|
||||
<Button
|
||||
v-else
|
||||
:severity="preselected ? 'primary' : 'secondary'"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<Divider />
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<h3 class="text-muted text-sm font-medium">
|
||||
<h3 class="text-sm font-medium text-muted">
|
||||
{{ $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-muted text-xs">
|
||||
<div v-else-if="formattedLastUpdateTime" class="text-xs text-muted">
|
||||
{{ $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-muted text-xs">{{ data.timestamp }}</div>
|
||||
<div class="text-xs text-muted">{{ data.timestamp }}</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="amount" :header="$t('g.amount')">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<TabPanel :value="props.value" class="size-full" :class="props.class">
|
||||
<div class="flex size-full flex-col gap-2">
|
||||
<TabPanel :value="props.value" class="h-full w-full" :class="props.class">
|
||||
<div class="flex h-full w-full flex-col gap-2">
|
||||
<slot name="header" />
|
||||
<ScrollPanel class="h-0 grow pr-2">
|
||||
<slot />
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<div class="font-semibold">
|
||||
{{ data.params?.api_name || 'API' }}
|
||||
</div>
|
||||
<div class="text-smoke-400 text-sm">
|
||||
<div class="text-sm text-gray-400">
|
||||
{{ $t('credits.model') }}: {{ data.params?.model || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<h3 class="font-medium">
|
||||
{{ $t('userSettings.provider') }}
|
||||
</h3>
|
||||
<div class="text-muted flex items-center gap-1">
|
||||
<div class="flex items-center gap-1 text-muted">
|
||||
<i :class="providerIcon" />
|
||||
{{ providerName }}
|
||||
<Button
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<ProgressSpinner
|
||||
v-if="loading"
|
||||
class="mt-4 size-8"
|
||||
class="mt-4 h-8 w-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-smoke-600">
|
||||
<p class="text-gray-600">
|
||||
{{ $t('auth.login.title') }}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -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 font-medium leading-normal">
|
||||
<h1 class="my-0 text-2xl leading-normal font-medium">
|
||||
{{ t('auth.apiKey.title') }}
|
||||
</h1>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-muted my-0 text-base">
|
||||
<p class="my-0 text-base text-muted">
|
||||
{{ t('auth.apiKey.description') }}
|
||||
</p>
|
||||
<a
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
{{ t('auth.login.passwordLabel') }}
|
||||
</label>
|
||||
<span
|
||||
class="text-muted cursor-pointer select-none text-base font-medium"
|
||||
class="cursor-pointer text-base font-medium text-muted select-none"
|
||||
:class="{
|
||||
'text-link-disabled': !$form.email?.value || $form.email?.invalid
|
||||
}"
|
||||
@@ -60,7 +60,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<ProgressSpinner v-if="loading" class="size-8" />
|
||||
<ProgressSpinner v-if="loading" class="h-8 w-8" />
|
||||
<Button
|
||||
v-else
|
||||
type="submit"
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
<Button
|
||||
ref="buttonRef"
|
||||
severity="secondary"
|
||||
class="group h-8 rounded-none! bg-comfy-menu-bg p-0 transition-none! hover:rounded-lg! hover:bg-interface-button-hover-surface!"
|
||||
class="group h-8 rounded-none! bg-interface-panel-surface p-0 transition-none! hover:rounded-lg! hover:bg-button-hover-surface!"
|
||||
:style="buttonStyles"
|
||||
@click="toggle"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex items-center gap-1 pr-0.5">
|
||||
<div
|
||||
class="rounded-lg bg-interface-panel-selected-surface p-2 group-hover:bg-interface-button-hover-surface"
|
||||
class="rounded-lg bg-button-active-surface p-2 group-hover:bg-button-hover-surface"
|
||||
>
|
||||
<i :class="currentModeIcon" class="block size-4" />
|
||||
<i :class="currentModeIcon" class="block h-4 w-4" />
|
||||
</div>
|
||||
<i class="icon-[lucide--chevron-down] block size-4 pr-1.5" />
|
||||
<i class="icon-[lucide--chevron-down] block h-4 w-4 pr-1.5" />
|
||||
</div>
|
||||
</template>
|
||||
</Button>
|
||||
@@ -29,27 +29,27 @@
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div
|
||||
class="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between px-3 py-2 text-sm"
|
||||
class="flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
|
||||
@click="setMode('select')"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="icon-[lucide--mouse-pointer-2] size-4" />
|
||||
<i class="icon-[lucide--mouse-pointer-2] h-4 w-4" />
|
||||
<span>{{ $t('graphCanvasMenu.select') }}</span>
|
||||
</div>
|
||||
<span class="text-text-primary text-[9px]">{{
|
||||
<span class="text-[9px] text-text-primary">{{
|
||||
unlockCommandText
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm"
|
||||
class="flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
|
||||
@click="setMode('hand')"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="icon-[lucide--hand] size-4" />
|
||||
<i class="icon-[lucide--hand] h-4 w-4" />
|
||||
<span>{{ $t('graphCanvasMenu.hand') }}</span>
|
||||
</div>
|
||||
<span class="text-text-primary text-[9px]">{{ lockCommandText }}</span>
|
||||
<span class="text-[9px] text-text-primary">{{ lockCommandText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
@@ -114,7 +114,7 @@ const popoverPt = computed(() => ({
|
||||
content: {
|
||||
class: [
|
||||
'mb-2 text-text-primary',
|
||||
'shadow-lg border border-interface-stroke',
|
||||
'shadow-lg border border-node-border',
|
||||
'bg-nav-background',
|
||||
'rounded-lg',
|
||||
'p-2 px-3',
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<LiteGraphCanvasSplitterOverlay v-if="comfyAppReady">
|
||||
<template v-if="showUI && workflowTabsPosition === 'Topbar'" #workflow-tabs>
|
||||
<div
|
||||
class="workflow-tabs-container h-9.5 pointer-events-auto relative w-full"
|
||||
class="workflow-tabs-container pointer-events-auto relative h-9.5 w-full"
|
||||
>
|
||||
<!-- Native drag area for Electron -->
|
||||
<div
|
||||
v-if="isNativeWindow() && workflowTabsPosition !== 'Topbar'"
|
||||
class="app-drag fixed left-0 top-0 z-10 h-[var(--comfy-topbar-height)] w-full"
|
||||
class="app-drag fixed top-0 left-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 size-full overflow-y-auto overflow-x-hidden"
|
||||
class="sidebar-content-container h-full w-full overflow-x-hidden overflow-y-auto"
|
||||
>
|
||||
<ExtensionSlot v-if="activeSidebarTab" :extension="activeSidebarTab" />
|
||||
</div>
|
||||
|
||||
@@ -5,22 +5,20 @@
|
||||
<!-- Backdrop -->
|
||||
<div
|
||||
v-if="hasActivePopup"
|
||||
class="z-1200 fixed inset-0"
|
||||
class="fixed inset-0 z-1200"
|
||||
@click="hideModal"
|
||||
></div>
|
||||
|
||||
<ButtonGroup
|
||||
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
|
||||
}"
|
||||
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"
|
||||
@wheel="canvasInteractions.handleWheel"
|
||||
>
|
||||
<CanvasModeSelector
|
||||
:button-styles="stringifiedMinimapStyles.buttonStyles"
|
||||
/>
|
||||
|
||||
<div class="bg-node-divider h-[27px] w-px self-center" />
|
||||
<div class="h-[27px] w-[1px] self-center bg-node-divider" />
|
||||
|
||||
<Button
|
||||
v-tooltip.top="fitViewTooltip"
|
||||
@@ -28,11 +26,11 @@
|
||||
icon="pi pi-expand"
|
||||
:aria-label="fitViewTooltip"
|
||||
:style="stringifiedMinimapStyles.buttonStyles"
|
||||
class="h-8 w-8 bg-comfy-menu-bg p-0 hover:bg-interface-button-hover-surface!"
|
||||
class="h-8 w-8 bg-interface-panel-surface p-0 hover:bg-button-hover-surface!"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--focus] size-4" />
|
||||
<i class="icon-[lucide--focus] h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
@@ -49,11 +47,11 @@
|
||||
>
|
||||
<span class="inline-flex items-center gap-1 px-2 text-xs">
|
||||
<span>{{ canvasStore.appScalePercentage }}%</span>
|
||||
<i class="icon-[lucide--chevron-down] size-4" />
|
||||
<i class="icon-[lucide--chevron-down] h-4 w-4" />
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<div class="bg-node-divider h-[27px] w-px self-center" />
|
||||
<div class="h-[27px] w-[1px] self-center bg-node-divider" />
|
||||
|
||||
<Button
|
||||
ref="minimapButton"
|
||||
@@ -66,7 +64,7 @@
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleMinimap')"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--map] size-4" />
|
||||
<i class="icon-[lucide--map] h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
@@ -87,7 +85,7 @@
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--route-off] size-4" />
|
||||
<i class="icon-[lucide--route-off] h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
@@ -165,18 +163,18 @@ const minimapCommandText = computed(() =>
|
||||
|
||||
// Computed properties for button classes and states
|
||||
const zoomButtonClass = computed(() => [
|
||||
'bg-comfy-menu-bg',
|
||||
isModalVisible.value ? 'not-active:bg-interface-panel-selected-surface!' : '',
|
||||
'hover:bg-interface-button-hover-surface!',
|
||||
'bg-interface-panel-surface',
|
||||
isModalVisible.value ? 'not-active:bg-button-active-surface!' : '',
|
||||
'hover:bg-button-hover-surface!',
|
||||
'p-0',
|
||||
'h-8',
|
||||
'w-15'
|
||||
])
|
||||
|
||||
const minimapButtonClass = computed(() => ({
|
||||
'bg-comfy-menu-bg': true,
|
||||
'hover:bg-interface-button-hover-surface!': true,
|
||||
'not-active:bg-interface-panel-selected-surface!': settingStore.get(
|
||||
'bg-interface-panel-surface': true,
|
||||
'hover:bg-button-hover-surface!': true,
|
||||
'not-active:bg-button-active-surface!': settingStore.get(
|
||||
'Comfy.Minimap.Visible'
|
||||
),
|
||||
'p-0': true,
|
||||
@@ -208,9 +206,9 @@ const linkVisibilityAriaLabel = computed(() =>
|
||||
: t('graphCanvasMenu.hideLinks')
|
||||
)
|
||||
const linkVisibleClass = computed(() => [
|
||||
'bg-comfy-menu-bg',
|
||||
linkHidden.value ? 'not-active:bg-interface-panel-selected-surface!' : '',
|
||||
'hover:bg-interface-button-hover-surface!',
|
||||
'bg-interface-panel-surface',
|
||||
linkHidden.value ? 'not-active:bg-button-active-surface!' : '',
|
||||
'hover:bg-button-hover-surface!',
|
||||
'p-0',
|
||||
'w-8',
|
||||
'h-8'
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<div
|
||||
ref="toolboxRef"
|
||||
style="transform: translate(var(--tb-x), var(--tb-y))"
|
||||
class="pointer-events-none fixed left-0 top-0 z-40"
|
||||
class="pointer-events-none fixed top-0 left-0 z-40"
|
||||
>
|
||||
<Transition name="slide-up">
|
||||
<Panel
|
||||
v-if="visible"
|
||||
class="selection-toolbox border-interface-stroke bg-interface-panel-surface pointer-events-auto rounded-lg border"
|
||||
class="selection-toolbox pointer-events-auto rounded-lg border border-interface-stroke bg-interface-panel-surface"
|
||||
:pt="{
|
||||
header: 'hidden',
|
||||
content: 'p-2 h-12 flex flex-row gap-1'
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="z-1300 border-0! bg-inherit! absolute bottom-[62px] right-0 flex w-[250px] justify-center"
|
||||
class="absolute right-0 bottom-[62px] z-1300 flex w-[250px] justify-center border-0! bg-inherit!"
|
||||
>
|
||||
<div
|
||||
class="w-4/5 rounded-lg border border-interface-stroke bg-interface-panel-surface p-2 text-text-primary shadow-lg select-none"
|
||||
class="w-4/5 rounded-lg border border-node-border 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="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm"
|
||||
class="flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
|
||||
@mousedown="startRepeat('Comfy.Canvas.ZoomIn')"
|
||||
@mouseup="stopRepeat"
|
||||
@mouseleave="stopRepeat"
|
||||
>
|
||||
<span class="font-medium">{{ $t('graphCanvasMenu.zoomIn') }}</span>
|
||||
<span class="text-text-primary text-[9px]">{{
|
||||
<span class="text-[9px] text-text-primary">{{
|
||||
zoomInCommandText
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm"
|
||||
class="flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
|
||||
@mousedown="startRepeat('Comfy.Canvas.ZoomOut')"
|
||||
@mouseup="stopRepeat"
|
||||
@mouseleave="stopRepeat"
|
||||
>
|
||||
<span class="font-medium">{{ $t('graphCanvasMenu.zoomOut') }}</span>
|
||||
<span class="text-text-primary text-[9px]">{{
|
||||
<span class="text-[9px] text-text-primary">{{
|
||||
zoomOutCommandText
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="hover:bg-node-component-surface-hovered flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm"
|
||||
class="flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered"
|
||||
@click="executeCommand('Comfy.Canvas.FitView')"
|
||||
>
|
||||
<span class="font-medium">{{ $t('zoomControls.zoomToFit') }}</span>
|
||||
<span class="text-text-primary text-[9px]">{{
|
||||
<span class="text-[9px] text-text-primary">{{
|
||||
zoomToFitCommandText
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="zoomInputContainer"
|
||||
class="zoomInputContainer bg-input-surface flex items-center gap-1 rounded p-2"
|
||||
class="zoomInputContainer flex items-center gap-1 rounded bg-input-surface p-2"
|
||||
>
|
||||
<InputNumber
|
||||
ref="zoomInput"
|
||||
@@ -60,7 +60,7 @@
|
||||
@input="applyZoom"
|
||||
@keyup.enter="applyZoom"
|
||||
/>
|
||||
<span class="text-text-primary shrink-0 text-sm">%</span>
|
||||
<span class="flex-shrink-0 text-sm text-text-primary">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -148,6 +148,6 @@ watch(
|
||||
</script>
|
||||
<style>
|
||||
.zoomInputContainer:focus-within {
|
||||
border: 1px solid var(--color-white);
|
||||
border: 1px solid var(--color-pure-white);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -92,7 +92,7 @@ describe('BypassButton', () => {
|
||||
const button = wrapper.find('button')
|
||||
|
||||
expect(button.classes()).not.toContain(
|
||||
'dark-theme:[&:not(:active)]:!bg-charcoal-600'
|
||||
'dark-theme:[&:not(:active)]:!bg-[#262729]'
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
severity="secondary"
|
||||
text
|
||||
data-testid="bypass-button"
|
||||
class="hover:bg-secondary-background"
|
||||
class="hover:bg-[#E7E6E6] hover:dark-theme:bg-charcoal-600"
|
||||
@click="toggleBypass"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--ban] size-4" />
|
||||
<i class="icon-[lucide--ban] h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
>
|
||||
<div class="flex items-center gap-1 px-0">
|
||||
<i
|
||||
class="pi pi-circle-fill size-4"
|
||||
class="pi pi-circle-fill h-4 w-4"
|
||||
:style="{ color: currentColor ?? '' }"
|
||||
/>
|
||||
<i
|
||||
class="pi pi-chevron-down size-4 py-1"
|
||||
class="pi pi-chevron-down h-4 w-4 py-1"
|
||||
:style="{ fontSize: '0.5rem' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@click="() => commandStore.execute('Comfy.Graph.UnpackSubgraph')"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--expand] size-4" />
|
||||
<i class="icon-[lucide--expand] h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
value: t('selectionToolbox.executeButton.tooltip'),
|
||||
showDelay: 1000
|
||||
}"
|
||||
class="bg-azure-400 dark-theme:bg-azure-600 size-8 !p-0"
|
||||
class="size-8 bg-[#31B9F4] !p-0 dark-theme:bg-[#0B8CE9]"
|
||||
text
|
||||
@mouseenter="() => handleMouseEnter()"
|
||||
@mouseleave="() => handleMouseLeave()"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
severity="secondary"
|
||||
@click="frameNodes"
|
||||
>
|
||||
<i class="icon-[lucide--frame] size-4" />
|
||||
<i class="icon-[lucide--frame] h-4 w-4" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
severity="secondary"
|
||||
@click="toggleHelp"
|
||||
>
|
||||
<i class="icon-[lucide--info] size-4" />
|
||||
<i class="icon-[lucide--info] h-4 w-4" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="option.type === 'divider'"
|
||||
class="bg-smoke-200 dark-theme:bg-zinc-700 my-1 h-px"
|
||||
class="my-1 h-px bg-gray-200 dark-theme:bg-zinc-700"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
role="button"
|
||||
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"
|
||||
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"
|
||||
@click="handleClick"
|
||||
>
|
||||
<i v-if="option.icon" :class="[option.icon, 'size-4']" />
|
||||
<i v-if="option.icon" :class="[option.icon, 'h-4 w-4']" />
|
||||
<span class="flex-1">{{ option.label }}</span>
|
||||
<span
|
||||
v-if="option.shortcut"
|
||||
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"
|
||||
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"
|
||||
>
|
||||
{{ option.shortcut }}
|
||||
</span>
|
||||
@@ -27,11 +27,11 @@
|
||||
:severity="option.badge === 'new' ? 'info' : 'secondary'"
|
||||
:value="t(option.badge)"
|
||||
:class="{
|
||||
'rounded-4xl bg-azure-400 dark-theme:bg-azure-600':
|
||||
'rounded-4xl bg-[#31B9F4] dark-theme:bg-[#0B8CE9]':
|
||||
option.badge === 'new',
|
||||
'rounded-4xl dark-theme:bg-black bg-slate-100':
|
||||
'rounded-4xl bg-[#9C9EAB] dark-theme:bg-[#000]':
|
||||
option.badge === 'deprecated',
|
||||
'h-4 gap-2.5 px-1 text-[9px] uppercase text-white': true
|
||||
'h-4 gap-2.5 px-1 text-[9px] text-white uppercase': true
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
}"
|
||||
data-testid="more-options-button"
|
||||
text
|
||||
class="size-8 px-0"
|
||||
class="h-8 w-8 px-0"
|
||||
severity="secondary"
|
||||
@click="handleClick"
|
||||
>
|
||||
<i class="icon-[lucide--more-vertical] size-4" />
|
||||
<i class="icon-[lucide--more-vertical] h-4 w-4" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
data-testid="refresh-button"
|
||||
@click="refreshSelected"
|
||||
>
|
||||
<i class="icon-[lucide--refresh-cw] size-4" />
|
||||
<i class="icon-[lucide--refresh-cw] h-4 w-4" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -20,23 +20,23 @@
|
||||
:key="subOption.label"
|
||||
:class="
|
||||
isColorSubmenu
|
||||
? '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'
|
||||
? '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'
|
||||
"
|
||||
:title="subOption.label"
|
||||
@click="handleSubmenuClick(subOption)"
|
||||
>
|
||||
<div
|
||||
v-if="subOption.color"
|
||||
class="border-smoke-300 dark-theme:border-zinc-600 size-5 rounded-full border"
|
||||
class="h-5 w-5 rounded-full border border-gray-300 dark-theme:border-zinc-600"
|
||||
:style="{ backgroundColor: subOption.color }"
|
||||
/>
|
||||
<template v-else-if="!subOption.color">
|
||||
<i
|
||||
v-if="isShapeSelected(subOption)"
|
||||
class="icon-[lucide--check] size-4 shrink-0"
|
||||
class="icon-[lucide--check] h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<div v-else class="w-4 shrink-0" />
|
||||
<div v-else class="w-4 flex-shrink-0" />
|
||||
<span>{{ subOption.label }}</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-smoke-300/10 dark-theme:bg-smoke-600/10 h-6 w-px self-center"
|
||||
/>
|
||||
<div class="h-6 w-px self-center bg-gray-300/10 dark-theme:bg-gray-600/10" />
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ScrollPanel
|
||||
ref="scrollPanelRef"
|
||||
class="min-h-[400px] w-full rounded-lg p-2 text-xs"
|
||||
class="min-h-[400px] w-full rounded-lg px-2 py-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="bg-smoke-300 dark-theme:bg-smoke-800 max-w-[80%] rounded-xl px-4 py-1 text-right"
|
||||
class="max-w-[80%] rounded-xl bg-gray-300 px-4 py-1 text-right dark-theme:bg-gray-800"
|
||||
>
|
||||
<div class="break-words text-[12px]">{{ item.prompt }}</div>
|
||||
<div class="text-[12px] break-words">{{ item.prompt }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2 mr-1 flex justify-end">
|
||||
<div class="mr-1 mb-2 flex justify-end">
|
||||
<CopyButton :text="item.prompt" />
|
||||
<Button
|
||||
v-tooltip="
|
||||
@@ -26,7 +26,7 @@
|
||||
"
|
||||
text
|
||||
rounded
|
||||
class="h-4! w-4! p-1! text-smoke-400 hover:text-smoke-600 hover:dark-theme:text-smoke-200 transition"
|
||||
class="h-4! w-4! p-1! text-gray-400 transition hover:text-gray-600 hover:dark-theme:text-gray-200"
|
||||
pt:icon:class="text-xs!"
|
||||
:icon="editIndex === i ? 'pi pi-times' : 'pi pi-pencil'"
|
||||
:aria-label="
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"
|
||||
text
|
||||
rounded
|
||||
class="h-4! w-6! p-1! text-smoke-400 hover:text-smoke-600 hover:dark-theme:text-smoke-200 transition"
|
||||
class="h-4! w-6! p-1! text-gray-400 transition hover:text-gray-600 hover:dark-theme:text-gray-200"
|
||||
pt:icon:class="text-xs!"
|
||||
:icon="copied ? 'pi pi-check' : 'pi pi-copy'"
|
||||
:aria-label="
|
||||
|
||||
@@ -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="break-words text-[12px]">
|
||||
<div class="text-[12px] break-words">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="help-center-menu" role="menu" aria-label="Help Center Menu">
|
||||
<div
|
||||
class="help-center-menu"
|
||||
role="menu"
|
||||
:aria-label="$t('helpCenter.helpFeedback')"
|
||||
>
|
||||
<!-- Main Menu Items -->
|
||||
<nav class="help-menu-section" role="menubar">
|
||||
<button
|
||||
@@ -68,7 +72,11 @@
|
||||
<h3 class="section-description">{{ $t('helpCenter.whatsNew') }}</h3>
|
||||
|
||||
<!-- Release Items -->
|
||||
<div v-if="hasReleases" role="group" aria-label="Recent releases">
|
||||
<div
|
||||
v-if="hasReleases"
|
||||
role="group"
|
||||
:aria-label="$t('helpCenter.recentReleases')"
|
||||
>
|
||||
<article
|
||||
v-for="release in releaseStore.recentReleases"
|
||||
:key="release.id || release.version"
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
: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"
|
||||
v-bind="attributes"
|
||||
:stroke="color"
|
||||
stroke-width="1"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -20,18 +22,11 @@ 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>
|
||||
@@ -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-azure-400 dark-theme:border-blue-700 rounded-lg 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">
|
||||
<h3 class="text-lg font-semibold mb-2">🎯 Keyboard Navigation Test</h3>
|
||||
<p class="text-sm text-smoke-600 dark-theme:text-smoke-300 mb-4">
|
||||
<p class="text-sm text-gray-600 dark-theme:text-gray-300 mb-4">
|
||||
Use your keyboard to navigate this MultiSelect:
|
||||
</p>
|
||||
<ol class="text-sm text-smoke-600 list-decimal list-inside space-y-1">
|
||||
<ol class="text-sm text-gray-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-smoke-700">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Select Frameworks (Keyboard Navigation Test)
|
||||
</label>
|
||||
<MultiSelect v-bind="args" class="w-80" />
|
||||
<p class="text-xs text-smoke-500">
|
||||
<p class="text-xs text-gray-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-smoke-600 mb-2">
|
||||
<p class="text-sm text-gray-600 mb-2">
|
||||
These dropdowns have proper ARIA attributes and labels for screen readers:
|
||||
</p>
|
||||
<ul class="text-sm text-smoke-600 list-disc list-inside space-y-1">
|
||||
<ul class="text-sm text-gray-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-smoke-700">
|
||||
<label class="block text-sm font-medium text-gray-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-smoke-500" aria-live="polite">
|
||||
<p class="text-xs text-gray-500" aria-live="polite">
|
||||
{{ selectedColors.length }} color(s) selected
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-smoke-700">
|
||||
<label class="block text-sm font-medium text-gray-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-smoke-500" aria-live="polite">
|
||||
<p class="text-xs text-gray-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-smoke-600 dark-theme:text-smoke-300 mb-4">
|
||||
<p class="text-sm text-gray-600 dark-theme:text-gray-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-smoke-700 mb-1">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
Before MultiSelect
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Previous field"
|
||||
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"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-smoke-700 mb-1">
|
||||
<label class="block text-sm font-medium text-gray-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-smoke-700 mb-1">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
After MultiSelect
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Next field"
|
||||
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"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -307,7 +307,7 @@ export const FocusManagement: Story = {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-smoke-600 mt-4">
|
||||
<div class="text-sm text-gray-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-smoke-200 dark-theme:border-zinc-700 rounded-lg p-6">
|
||||
<div class="bg-gray-50 dark-theme:bg-zinc-800 border border-gray-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-azure-400 dark-theme:border-blue-700 rounded-lg">
|
||||
<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">
|
||||
<h4 class="font-semibold mb-2">🎯 Quick Test</h4>
|
||||
<p class="text-sm text-smoke-700 dark-theme:text-smoke-300">
|
||||
<p class="text-sm text-gray-700 dark-theme:text-gray-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>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
v-if="showSearchBox || showSelectedCount || showClearButton"
|
||||
#header
|
||||
>
|
||||
<div class="flex flex-col px-2 pb-0 pt-2">
|
||||
<div class="flex flex-col px-2 pt-2 pb-0">
|
||||
<SearchBox
|
||||
v-if="showSearchBox"
|
||||
v-model="searchQuery"
|
||||
@@ -39,7 +39,7 @@
|
||||
>
|
||||
<span
|
||||
v-if="showSelectedCount"
|
||||
class="dark-theme:text-zinc-500 px-1 text-sm text-neutral-400"
|
||||
class="px-1 text-sm text-neutral-400 dark-theme:text-zinc-500"
|
||||
>
|
||||
{{
|
||||
selectedCount > 0
|
||||
@@ -52,22 +52,22 @@
|
||||
:label="$t('g.clearAll')"
|
||||
type="transparent"
|
||||
size="fit-content"
|
||||
class="dark-theme:text-blue-600 text-sm text-blue-500"
|
||||
class="text-sm text-blue-500 dark-theme:text-blue-600"
|
||||
@click.stop="selectedItems = []"
|
||||
/>
|
||||
</div>
|
||||
<div class="dark-theme:bg-zinc-700 my-4 h-px bg-zinc-200"></div>
|
||||
<div class="my-4 h-px bg-zinc-200 dark-theme:bg-zinc-700"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Trigger value (keep text scale identical) -->
|
||||
<template #value>
|
||||
<span class="dark-theme:text-smoke-200 text-sm text-zinc-700">
|
||||
<span class="text-sm text-zinc-700 dark-theme:text-gray-200">
|
||||
{{ label }}
|
||||
</span>
|
||||
<span
|
||||
v-if="selectedCount > 0"
|
||||
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"
|
||||
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"
|
||||
>
|
||||
{{ selectedCount }}
|
||||
</span>
|
||||
@@ -82,7 +82,7 @@
|
||||
<template #option="slotProps">
|
||||
<div class="flex items-center gap-2" :style="popoverStyle">
|
||||
<div
|
||||
class="flex size-4 shrink-0 items-center justify-center rounded p-0.5 transition-all duration-200"
|
||||
class="flex h-4 w-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'
|
||||
|
||||
@@ -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-azure-400 dark-theme:border-blue-700 rounded-lg 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">
|
||||
<h3 class="text-lg font-semibold mb-2">🎯 Keyboard Navigation Test</h3>
|
||||
<p class="text-sm text-smoke-600 dark-theme:text-smoke-300 mb-4">
|
||||
<p class="text-sm text-gray-600 dark-theme:text-gray-300 mb-4">
|
||||
Use your keyboard to navigate these SingleSelect dropdowns:
|
||||
</p>
|
||||
<ol class="text-sm text-smoke-600 dark-theme:text-smoke-300 list-decimal list-inside space-y-1">
|
||||
<ol class="text-sm text-gray-600 dark-theme:text-gray-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-smoke-700 dark-theme:text-smoke-200">
|
||||
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-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-smoke-500">
|
||||
<p class="text-xs text-gray-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-smoke-700 dark-theme:text-smoke-200">
|
||||
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200">
|
||||
Task Priority (With Icon)
|
||||
</label>
|
||||
<SingleSelect
|
||||
@@ -147,7 +147,7 @@ export const KeyboardNavigationDemo: Story = {
|
||||
</svg>
|
||||
</template>
|
||||
</SingleSelect>
|
||||
<p class="text-xs text-smoke-500">
|
||||
<p class="text-xs text-gray-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-smoke-600 dark-theme:text-smoke-300 mb-2">
|
||||
<p class="text-sm text-gray-600 dark-theme:text-gray-300 mb-2">
|
||||
These dropdowns have proper ARIA attributes and labels for screen readers:
|
||||
</p>
|
||||
<ul class="text-sm text-smoke-600 dark-theme:text-smoke-300 list-disc list-inside space-y-1">
|
||||
<ul class="text-sm text-gray-600 dark-theme:text-gray-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-smoke-700 dark-theme:text-smoke-200" id="language-label">
|
||||
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-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-smoke-500" aria-live="polite">
|
||||
<p class="text-xs text-gray-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-smoke-700 dark-theme:text-smoke-200" id="theme-label">
|
||||
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-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-smoke-500" aria-live="polite">
|
||||
<p class="text-xs text-gray-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-smoke-600 dark-theme:text-smoke-300 space-y-1">
|
||||
<ul class="text-sm text-gray-600 dark-theme:text-gray-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-smoke-600 dark-theme:text-smoke-300">
|
||||
<p class="text-sm text-gray-600 dark-theme:text-gray-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-smoke-700 dark-theme:text-smoke-200 mb-1">
|
||||
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
|
||||
Title *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
placeholder="Enter a title"
|
||||
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"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200 mb-1">
|
||||
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
|
||||
Category *
|
||||
</label>
|
||||
<SingleSelect
|
||||
@@ -332,7 +332,7 @@ export const FormIntegration: Story = {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200 mb-1">
|
||||
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
|
||||
Status
|
||||
</label>
|
||||
<SingleSelect
|
||||
@@ -344,7 +344,7 @@ export const FormIntegration: Story = {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200 mb-1">
|
||||
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
|
||||
Assignee
|
||||
</label>
|
||||
<SingleSelect
|
||||
@@ -356,13 +356,13 @@ export const FormIntegration: Story = {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-smoke-700 dark-theme:text-smoke-200 mb-1">
|
||||
<label class="block text-sm font-medium text-gray-700 dark-theme:text-gray-200 mb-1">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
rows="4"
|
||||
placeholder="Enter description"
|
||||
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"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -375,16 +375,16 @@ export const FormIntegration: Story = {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
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"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<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">
|
||||
<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">
|
||||
<h4 class="font-semibold mb-2">Current Form Data:</h4>
|
||||
<pre class="text-xs text-smoke-600 dark-theme:text-smoke-300">{{ JSON.stringify(formData, null, 2) }}</pre>
|
||||
<pre class="text-xs text-gray-600 dark-theme:text-gray-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-smoke-200 dark-theme:border-zinc-700 rounded-lg p-6">
|
||||
<div class="bg-gray-50 dark-theme:bg-zinc-800 border border-gray-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-azure-400 dark-theme:border-blue-700 rounded-lg">
|
||||
<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">
|
||||
<h4 class="font-semibold mb-2">🎯 Quick Test</h4>
|
||||
<p class="text-sm text-smoke-700 dark-theme:text-smoke-200">
|
||||
<p class="text-sm text-gray-700 dark-theme:text-gray-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-smoke-700 dark-theme:text-smoke-200">
|
||||
<p class="text-sm text-gray-700 dark-theme:text-gray-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>
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
<slot name="icon" />
|
||||
<span
|
||||
v-if="slotProps.value !== null && slotProps.value !== undefined"
|
||||
class="dark-theme:text-smoke-200 text-zinc-700"
|
||||
class="text-zinc-700 dark-theme:text-gray-200"
|
||||
>
|
||||
{{ getLabel(slotProps.value) }}
|
||||
</span>
|
||||
<span v-else class="dark-theme:text-smoke-200 text-zinc-700">
|
||||
<span v-else class="text-zinc-700 dark-theme:text-gray-200">
|
||||
{{ label }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@
|
||||
<span class="truncate">{{ option.name }}</span>
|
||||
<i
|
||||
v-if="selected"
|
||||
class="icon-[lucide--check] dark-theme:text-white text-neutral-600"
|
||||
class="icon-[lucide--check] text-neutral-600 dark-theme:text-white"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative size-full"
|
||||
class="relative h-full w-full"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
@@ -59,7 +59,7 @@
|
||||
/>
|
||||
<div
|
||||
v-if="enable3DViewer"
|
||||
class="pointer-events-auto absolute right-2 top-12 z-20"
|
||||
class="pointer-events-auto absolute top-12 right-2 z-20"
|
||||
>
|
||||
<ViewerControls :node="node" />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative size-full"
|
||||
class="relative h-full w-full"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
@@ -34,7 +34,7 @@
|
||||
@up-direction-change="listenUpDirectionChange"
|
||||
@recording-status-change="listenRecordingStatusChange"
|
||||
/>
|
||||
<div class="pointer-events-none absolute left-0 top-0 size-full">
|
||||
<div class="pointer-events-none absolute top-0 left-0 h-full w-full">
|
||||
<Load3DControls
|
||||
:input-spec="inputSpec"
|
||||
:background-color="backgroundColor"
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="showRecordingControls"
|
||||
class="pointer-events-auto absolute right-2 top-12 z-20"
|
||||
class="pointer-events-auto absolute top-12 right-2 z-20"
|
||||
>
|
||||
<RecordingControls
|
||||
:node="node"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="animations && animations.length > 0"
|
||||
class="pointer-events-auto absolute left-0 top-0 z-10 flex w-full items-center justify-center gap-2 pt-2"
|
||||
class="pointer-events-auto absolute top-0 left-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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-smoke-700/30 pointer-events-auto absolute left-2 top-12 z-20 flex flex-col rounded-lg"
|
||||
class="pointer-events-auto absolute top-12 left-2 z-20 flex flex-col rounded-lg bg-gray-700/30"
|
||||
>
|
||||
<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 left-12 top-0 rounded-lg bg-black/50 shadow-lg"
|
||||
class="absolute top-0 left-12 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-smoke-600': activeCategory === category }"
|
||||
:class="{ 'bg-gray-600': activeCategory === category }"
|
||||
@click="selectCategory(category)"
|
||||
>
|
||||
<i :class="getCategoryIcon(category)" />
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="activeCategory" class="bg-smoke-700/30 rounded-lg">
|
||||
<div v-show="activeCategory" class="rounded-lg bg-gray-700/30">
|
||||
<SceneControls
|
||||
v-if="activeCategory === 'scene'"
|
||||
ref="sceneControlsRef"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="container" class="comfy-load-3d relative size-full">
|
||||
<div ref="container" class="comfy-load-3d relative h-full w-full">
|
||||
<LoadingOverlay ref="loadingOverlayRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div ref="mainContentRef" class="relative flex-1">
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="comfy-load-3d-viewer absolute size-full"
|
||||
class="comfy-load-3d-viewer absolute h-full w-full"
|
||||
@resize="viewer.handleResize"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</Button>
|
||||
<div
|
||||
v-show="showFOV"
|
||||
class="absolute left-12 top-0 rounded-lg bg-black/50 p-4 shadow-lg"
|
||||
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg"
|
||||
style="width: 150px"
|
||||
>
|
||||
<Slider
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</Button>
|
||||
<div
|
||||
v-show="showExportFormats"
|
||||
class="absolute left-12 top-0 rounded-lg bg-black/50 shadow-lg"
|
||||
class="absolute top-0 left-12 rounded-lg bg-black/50 shadow-lg"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<Button
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</Button>
|
||||
<div
|
||||
v-show="showLightIntensity"
|
||||
class="absolute left-12 top-0 rounded-lg bg-black/50 p-4 shadow-lg"
|
||||
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg"
|
||||
style="width: 150px"
|
||||
>
|
||||
<Slider
|
||||
|
||||