Compare commits

...

11 Commits

Author SHA1 Message Date
bymyself
cb0861edf5 [style] apply review feedback: simplify conditional logic
Simplify conditional class logic in FormSelectButton.vue per review suggestion:
- Combine cursor classes into single ternary
- Combine background classes into nested ternary with !disabled guard
2025-11-06 13:05:27 -07:00
bymyself
6e2b20b5bf Merge main into vue-node/style/improvements
Update semantic token usage to match current design system conventions:
- Use bg-component-node-widget-background (main's naming)
- Use text-base-foreground for primary text (main's convention)
- Use text-secondary for icons and secondary text (semantic token)
- Use bg-interface-stroke and bg-button-icon (semantic tokens)
- Use hover:bg-interface-menu-component-surface-hovered
- Use bg-interface-menu-component-surface-selected

This preserves the PR's intent of migrating to semantic tokens while
adopting the evolved token names from main.
2025-11-05 13:38:17 -07:00
bymyself
44f485eeca revert config changes 2025-10-28 12:53:37 -07:00
bymyself
032f5f2ecf revert multiselect changes 2025-10-27 20:13:37 -07:00
bymyself
df5cd4ce04 [style] ignore temporarily disabled TailwindCSS ESLint dependencies in knip
Due to TailwindCSS v4 compatibility issues with eslint-plugin-tailwindcss v4.0.0-beta.0,
we temporarily disabled the plugin in eslint.config.ts. This causes knip to detect
these dependencies as unused, so we add them to ignoreDependencies until the
compatibility issue is resolved.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 08:31:20 -07:00
bymyself
883463cb54 [fix] temporarily disable TailwindCSS ESLint plugin and revert MultiSelect.vue
- Disable eslint-plugin-tailwindcss due to TailwindCSS v4 incompatibility
- Revert MultiSelect.vue changes as they were not part of vueNodes scope

The eslint-plugin-tailwindcss v4.0.0-beta.0 is incompatible with TailwindCSS v4.1.12
as it tries to load v3 file structure that no longer exists.
2025-10-27 08:31:20 -07:00
bymyself
b43c0a53bb [style] restore MultiSelect.vue semantic token changes
Restore the semantic token migration for MultiSelect.vue:
- text-neutral-400 dark-theme:text-zinc-500 → text-secondary

This completes the semantic token migration across all components.
2025-10-27 00:01:58 -07:00
bymyself
eddd8ba7fd [test] update FormSelectButton tests for semantic tokens
Update test expectations to use semantic color tokens instead of hard-coded classes:
- bg-white → bg-interface-menu-component-surface-selected (25 instances)
- text-neutral-900 → text-primary (3 instances)
- hover:bg-zinc-200/50 → hover:bg-interface-menu-component-surface-hovered (2 instances)
2025-10-26 23:05:47 -07:00
bymyself
4e1d49a097 [test] update WidgetSelectButton tests for semantic tokens
Update test expectations to use semantic color tokens instead of hard-coded classes:
- bg-white → bg-interface-menu-component-surface-selected
- text-neutral-900 → text-primary/text-secondary
- hover:bg-zinc-200/50 → hover:bg-interface-menu-component-surface-hovered
2025-10-26 23:05:47 -07:00
bymyself
20015ccd81 [style] complete semantic token migration for vueNodes widgets
Complete the semantic token migration by updating final hard-coded colors
in widget components to use design system tokens:

- FormSelectButton.vue: use text-primary/text-secondary consistently
- FormDropdownMenuFilter.vue: use text-secondary for default text
- FormDropdownMenuItem.vue: use text-secondary for metadata
- AudioPreviewPlayer.vue: use text-secondary for icons
- Remove hard-coded zinc, neutral, and other color values from vueNodes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 22:13:29 -07:00
bymyself
cd15b659ae [style] complete semantic token migration for remaining components
Complete the semantic token migration by updating final hard-coded colors
in widget components to use design system tokens:

- MultiSelect.vue: text-neutral-400 → text-secondary
- Ensure consistent text-primary/text-secondary usage across all widgets
- Remove hard-coded zinc, neutral, and other color values

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 22:13:29 -07:00
6 changed files with 156 additions and 85 deletions

View File

@@ -107,10 +107,14 @@ describe('WidgetSelectButton Button Selection', () => {
const selectedButton = buttons[1] // 'banana'
const unselectedButton = buttons[0] // 'apple'
expect(selectedButton.classes()).toContain('bg-white')
expect(selectedButton.classes()).toContain('text-neutral-900')
expect(unselectedButton.classes()).not.toContain('bg-white')
expect(unselectedButton.classes()).not.toContain('text-neutral-900')
expect(selectedButton.classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
expect(selectedButton.classes()).toContain('text-primary')
expect(unselectedButton.classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
expect(unselectedButton.classes()).toContain('text-secondary')
})
it('handles no selection gracefully', () => {
@@ -120,8 +124,10 @@ describe('WidgetSelectButton Button Selection', () => {
const buttons = wrapper.findAll('button')
buttons.forEach((button) => {
expect(button.classes()).not.toContain('bg-white')
expect(button.classes()).not.toContain('text-neutral-900')
expect(button.classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
expect(button.classes()).toContain('text-secondary')
})
})
@@ -134,13 +140,19 @@ describe('WidgetSelectButton Button Selection', () => {
// Initially 'first' is selected
let buttons = wrapper.findAll('button')
expect(buttons[0].classes()).toContain('bg-white')
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
// Update to 'second'
await wrapper.setProps({ modelValue: 'second' })
buttons = wrapper.findAll('button')
expect(buttons[0].classes()).not.toContain('bg-white')
expect(buttons[1].classes()).toContain('bg-white')
expect(buttons[0].classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
expect(buttons[1].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
})
})
@@ -222,7 +234,9 @@ describe('WidgetSelectButton Button Selection', () => {
expect(buttons[2].text()).toBe('3')
// The selected button should be the one with '2'
expect(buttons[1].classes()).toContain('bg-white')
expect(buttons[1].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('handles object options with label and value', () => {
@@ -240,7 +254,9 @@ describe('WidgetSelectButton Button Selection', () => {
expect(buttons[2].text()).toBe('Third Option')
// 'second' should be selected
expect(buttons[1].classes()).toContain('bg-white')
expect(buttons[1].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('emits correct values for object options', async () => {
@@ -277,7 +293,9 @@ describe('WidgetSelectButton Button Selection', () => {
const buttons = wrapper.findAll('button')
expect(buttons).toHaveLength(4)
expect(buttons[0].classes()).toContain('bg-white') // Empty string is selected
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
) // Empty string is selected
})
it('handles null/undefined in options', () => {
@@ -292,7 +310,9 @@ describe('WidgetSelectButton Button Selection', () => {
const buttons = wrapper.findAll('button')
expect(buttons).toHaveLength(4)
expect(buttons[0].classes()).toContain('bg-white')
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('handles very long option text', () => {
@@ -313,7 +333,9 @@ describe('WidgetSelectButton Button Selection', () => {
const buttons = wrapper.findAll('button')
expect(buttons).toHaveLength(20)
expect(buttons[4].classes()).toContain('bg-white') // option5 is at index 4
expect(buttons[4].classes()).toContain(
'bg-interface-menu-component-surface-selected'
) // option5 is at index 4
})
it('handles duplicate options', () => {
@@ -324,8 +346,12 @@ describe('WidgetSelectButton Button Selection', () => {
const buttons = wrapper.findAll('button')
expect(buttons).toHaveLength(4)
// Both 'duplicate' buttons should be highlighted (due to value matching)
expect(buttons[0].classes()).toContain('bg-white')
expect(buttons[2].classes()).toContain('bg-white')
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
expect(buttons[2].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
})
})
@@ -354,7 +380,9 @@ describe('WidgetSelectButton Button Selection', () => {
const buttons = wrapper.findAll('button')
const unselectedButton = buttons[1] // 'option2'
expect(unselectedButton.classes()).toContain('hover:bg-zinc-200/50')
expect(unselectedButton.classes()).toContain(
'hover:bg-interface-menu-component-surface-hovered'
)
expect(unselectedButton.classes()).toContain('cursor-pointer')
})
})

View File

@@ -24,17 +24,14 @@
role="button"
:tabindex="0"
aria-label="Play/Pause"
class="flex size-6 cursor-pointer items-center justify-center rounded hover:bg-black/10 dark-theme:hover:bg-white/10"
class="flex size-6 cursor-pointer items-center justify-center rounded hover:bg-interface-menu-component-surface-hovered"
@click="togglePlayPause"
>
<i
v-if="!isPlaying"
class="icon-[lucide--play] size-4 text-smoke-600 dark-theme:text-smoke-800"
/>
<i
v-else
class="icon-[lucide--pause] size-4 text-smoke-600 dark-theme:text-smoke-800"
class="text-secondary icon-[lucide--play] size-4"
/>
<i v-else class="text-secondary icon-[lucide--pause] size-4" />
</div>
<!-- Time Display -->
@@ -44,11 +41,9 @@
</div>
<!-- Progress Bar -->
<div
class="relative h-0.5 flex-1 rounded-full bg-smoke-300 dark-theme:bg-ash-800"
>
<div class="relative h-0.5 flex-1 rounded-full bg-interface-stroke">
<div
class="absolute top-0 left-0 h-full rounded-full bg-smoke-600 transition-all dark-theme:bg-white/50"
class="absolute top-0 left-0 h-full rounded-full bg-button-icon transition-all"
:style="{ width: `${progressPercentage}%` }"
/>
<input
@@ -70,21 +65,18 @@
role="button"
:tabindex="0"
aria-label="Volume"
class="flex size-6 cursor-pointer items-center justify-center rounded hover:bg-black/10 dark-theme:hover:bg-white/10"
class="flex size-6 cursor-pointer items-center justify-center rounded hover:bg-interface-menu-component-surface-hovered"
@click="toggleMute"
>
<i
v-if="showVolumeTwo"
class="icon-[lucide--volume-2] size-4 text-smoke-600 dark-theme:text-smoke-800"
class="text-secondary icon-[lucide--volume-2] size-4"
/>
<i
v-else-if="showVolumeOne"
class="icon-[lucide--volume-1] size-4 text-smoke-600 dark-theme:text-smoke-800"
/>
<i
v-else
class="icon-[lucide--volume-x] size-4 text-smoke-600 dark-theme:text-smoke-800"
class="text-secondary icon-[lucide--volume-1] size-4"
/>
<i v-else class="text-secondary icon-[lucide--volume-x] size-4" />
</div>
<!-- Options Button -->
@@ -94,12 +86,10 @@
role="button"
:tabindex="0"
aria-label="More Options"
class="flex size-6 cursor-pointer items-center justify-center rounded hover:bg-black/10 dark-theme:hover:bg-white/10"
class="flex size-6 cursor-pointer items-center justify-center rounded hover:bg-interface-menu-component-surface-hovered"
@click="toggleOptionsMenu"
>
<i
class="icon-[lucide--more-vertical] size-4 text-smoke-600 dark-theme:text-smoke-800"
/>
<i class="text-secondary icon-[lucide--more-vertical] size-4" />
</div>
</div>

View File

@@ -124,10 +124,16 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('option2', options)
const buttons = wrapper.findAll('button')
expect(buttons[1].classes()).toContain('bg-white')
expect(buttons[1].classes()).toContain('text-neutral-900')
expect(buttons[0].classes()).not.toContain('bg-white')
expect(buttons[2].classes()).not.toContain('bg-white')
expect(buttons[1].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
expect(buttons[1].classes()).toContain('text-primary')
expect(buttons[0].classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
expect(buttons[2].classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
})
})
@@ -159,8 +165,10 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('200', options)
const buttons = wrapper.findAll('button')
expect(buttons[1].classes()).toContain('bg-white')
expect(buttons[1].classes()).toContain('text-neutral-900')
expect(buttons[1].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
expect(buttons[1].classes()).toContain('text-primary')
})
})
@@ -201,9 +209,15 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('md', options)
const buttons = wrapper.findAll('button')
expect(buttons[1].classes()).toContain('bg-white') // Medium
expect(buttons[0].classes()).not.toContain('bg-white')
expect(buttons[2].classes()).not.toContain('bg-white')
expect(buttons[1].classes()).toContain(
'bg-interface-menu-component-surface-selected'
) // Medium
expect(buttons[0].classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
expect(buttons[2].classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('handles objects without value field', () => {
@@ -216,7 +230,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
expect(buttons[0].text()).toBe('First')
expect(buttons[1].text()).toBe('Second')
expect(buttons[0].classes()).toContain('bg-white')
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('handles objects without label field', () => {
@@ -253,8 +269,12 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('first_id', options, { optionValue: 'id' })
const buttons = wrapper.findAll('button')
expect(buttons[0].classes()).toContain('bg-white')
expect(buttons[1].classes()).not.toContain('bg-white')
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
expect(buttons[1].classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('emits custom optionValue when clicked', async () => {
@@ -301,7 +321,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
buttons.forEach((button) => {
expect(button.classes()).not.toContain('hover:bg-zinc-200/50')
expect(button.classes()).not.toContain(
'hover:bg-interface-menu-component-surface-hovered'
)
expect(button.classes()).not.toContain('cursor-pointer')
})
})
@@ -311,7 +333,9 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('option1', options, { disabled: true })
const buttons = wrapper.findAll('button')
expect(buttons[0].classes()).not.toContain('bg-white') // Selected styling disabled
expect(buttons[0].classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
) // Selected styling disabled
expect(buttons[0].classes()).toContain('opacity-50')
expect(buttons[0].classes()).toContain('text-secondary')
})
@@ -324,7 +348,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
buttons.forEach((button) => {
expect(button.classes()).not.toContain('bg-white')
expect(button.classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
})
})
@@ -334,7 +360,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
buttons.forEach((button) => {
expect(button.classes()).not.toContain('bg-white')
expect(button.classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
})
})
@@ -343,8 +371,12 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('', options)
const buttons = wrapper.findAll('button')
expect(buttons[0].classes()).toContain('bg-white') // Empty string is selected
expect(buttons[1].classes()).not.toContain('bg-white')
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
) // Empty string is selected
expect(buttons[1].classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('compares values as strings', () => {
@@ -352,7 +384,9 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('1', options)
const buttons = wrapper.findAll('button')
expect(buttons[0].classes()).toContain('bg-white') // '1' matches number 1 as string
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
) // '1' matches number 1 as string
})
})
@@ -362,8 +396,10 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('option1', options)
const selectedButton = wrapper.findAll('button')[0]
expect(selectedButton.classes()).toContain('bg-white')
expect(selectedButton.classes()).toContain('text-neutral-900')
expect(selectedButton.classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
expect(selectedButton.classes()).toContain('text-primary')
})
it('applies unselected styling to inactive options', () => {
@@ -380,7 +416,9 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('option1', options, { disabled: false })
const unselectedButton = wrapper.findAll('button')[1]
expect(unselectedButton.classes()).toContain('hover:bg-zinc-200/50')
expect(unselectedButton.classes()).toContain(
'hover:bg-interface-menu-component-surface-hovered'
)
expect(unselectedButton.classes()).toContain('cursor-pointer')
})
})
@@ -403,7 +441,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
expect(buttons[0].text()).toBe('@#$%^&*()')
expect(buttons[0].classes()).toContain('bg-white')
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('handles unicode characters in options', () => {
@@ -412,7 +452,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
expect(buttons[0].text()).toBe('🎨 Art')
expect(buttons[0].classes()).toContain('bg-white')
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('handles duplicate option values', () => {
@@ -420,9 +462,15 @@ describe('FormSelectButton Core Component', () => {
const wrapper = mountComponent('duplicate', duplicateOptions)
const buttons = wrapper.findAll('button')
expect(buttons[0].classes()).toContain('bg-white')
expect(buttons[2].classes()).toContain('bg-white') // Both duplicates selected
expect(buttons[1].classes()).not.toContain('bg-white')
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
expect(buttons[2].classes()).toContain(
'bg-interface-menu-component-surface-selected'
) // Both duplicates selected
expect(buttons[1].classes()).not.toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('handles mixed type options safely', () => {
@@ -436,7 +484,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
expect(buttons).toHaveLength(4)
expect(buttons[1].classes()).toContain('bg-white') // Number 123 as string
expect(buttons[1].classes()).toContain(
'bg-interface-menu-component-surface-selected'
) // Number 123 as string
})
it('handles objects with missing properties gracefully', () => {
@@ -450,7 +500,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
expect(buttons).toHaveLength(4)
expect(buttons[2].classes()).toContain('bg-white')
expect(buttons[2].classes()).toContain(
'bg-interface-menu-component-surface-selected'
)
})
it('handles large number of options', () => {
@@ -462,7 +514,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
expect(buttons).toHaveLength(50)
expect(buttons[24].classes()).toContain('bg-white') // Option 25 at index 24
expect(buttons[24].classes()).toContain(
'bg-interface-menu-component-surface-selected'
) // Option 25 at index 24
})
it('fallback to index when all object properties are missing', () => {
@@ -474,7 +528,9 @@ describe('FormSelectButton Core Component', () => {
const buttons = wrapper.findAll('button')
expect(buttons).toHaveLength(2)
expect(buttons[0].classes()).toContain('bg-white') // Falls back to index 0
expect(buttons[0].classes()).toContain(
'bg-interface-menu-component-surface-selected'
) // Falls back to index 0
})
})

View File

@@ -15,15 +15,12 @@
'flex-1 h-6 px-5 py-[5px] rounded flex justify-center items-center gap-1 transition-all duration-150 ease-in-out',
'bg-transparent border-none',
'text-center text-xs font-normal',
{
'bg-white': isSelected(option) && !disabled,
'hover:bg-zinc-200/50': !isSelected(option) && !disabled,
'opacity-50 cursor-not-allowed': disabled,
'cursor-pointer': !disabled
},
isSelected(option) && !disabled
? 'text-neutral-900'
: 'text-secondary'
disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
!disabled &&
(isSelected(option)
? 'bg-interface-menu-component-surface-selected'
: 'hover:bg-interface-menu-component-surface-hovered'),
isSelected(option) && !disabled ? 'text-primary' : 'text-secondary'
)
"
:disabled="disabled"

View File

@@ -11,7 +11,7 @@ const filterSelected = defineModel<OptionId>('filterSelected')
</script>
<template>
<div class="mb-4 flex gap-1 px-4 text-zinc-400">
<div class="text-secondary mb-4 flex gap-1 px-4">
<div
v-for="option in filterOptions"
:key="option.id"
@@ -19,10 +19,10 @@ const filterSelected = defineModel<OptionId>('filterSelected')
cn(
'px-4 py-2 rounded-md inline-flex justify-center items-center cursor-pointer select-none',
'transition-all duration-150',
'hover:text-base-foreground hover:bg-zinc-500/10',
'hover:text-base-foreground hover:bg-interface-menu-component-surface-hovered',
'active:scale-95',
filterSelected === option.id
? '!bg-zinc-500/20 text-base-foreground'
? '!bg-interface-menu-component-surface-selected text-base-foreground'
: 'bg-transparent'
)
"

View File

@@ -60,9 +60,9 @@ function handleVideoLoad(event: Event) {
'transition-all duration-150',
{
'flex-col text-center': layout === 'grid',
'flex-row text-left max-h-16 bg-zinc-500/20 rounded-lg hover:scale-102 active:scale-98':
'flex-row text-left max-h-16 bg-interface-menu-component-surface-hovered rounded-lg hover:scale-102 active:scale-98':
layout === 'list',
'flex-row text-left hover:bg-zinc-500/20 rounded-lg':
'flex-row text-left hover:bg-interface-menu-component-surface-hovered rounded-lg':
layout === 'list-small',
// selection
'ring-2 ring-blue-500': layout === 'list' && selected
@@ -77,7 +77,7 @@ function handleVideoLoad(event: Event) {
:class="
cn(
'relative',
'w-full aspect-square overflow-hidden outline-1 outline-offset-[-1px] outline-zinc-300/10',
'w-full aspect-square overflow-hidden outline-1 outline-offset-[-1px] outline-interface-stroke',
'transition-all duration-150',
{
'min-w-16 max-w-16 rounded-l-lg': layout === 'list',
@@ -142,7 +142,7 @@ function handleVideoLoad(event: Event) {
{{ name }}
</span>
<!-- Meta Data -->
<span class="block text-xs text-slate-400">{{
<span class="text-secondary block text-xs">{{
metadata || actualDimensions
}}</span>
</div>