-
Notifications
You must be signed in to change notification settings - Fork 3.8k
improvement(editor): replace mode toggle buttons with a switch #7825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
j15z
merged 8 commits into
improvement/editor-deploy-experience
from
fix/compact-mode-switches-editor-deploy
Sep 15, 2026
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e0101fe
fix(editor): use compact mode switches across input controls
j15z 56152f4
fix(emcn): align compact switch corner radii
j15z 6ddc1f9
fix(emcn): fill compact switch segments to the border
j15z f718af7
fix(emcn): use white surface for unselected mode
j15z a9ecadc
fix(emcn): restore inset mode switch squares
j15z 6fc9aa1
improvement(emcn): match mode switch corners and inset to chip fields
j15z 09cc11a
fix(editor): keep mode switch rows at their original height
j15z e733205
fix(knowledge): keep connector mode switch focused across mode changes
j15z File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
131 changes: 131 additions & 0 deletions
131
...aceId]/knowledge/[id]/components/connector-config-fields/connector-config-fields.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| /** @vitest-environment jsdom */ | ||
| import { act } from 'react' | ||
| import { createRoot, type Root } from 'react-dom/client' | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' | ||
| import { ConnectorConfigFields } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/connector-config-fields/connector-config-fields' | ||
| import { | ||
| type ConfigFieldValue, | ||
| useConnectorConfigFields, | ||
| } from '@/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields' | ||
| import { gmailConnectorMeta } from '@/connectors/gmail/meta' | ||
|
|
||
| vi.mock('@/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field', () => ({ | ||
| ConnectorSelectorField: ({ value }: { value: ConfigFieldValue }) => ( | ||
| <span data-testid='selector-value'>{Array.isArray(value) ? value.join(',') : value}</span> | ||
| ), | ||
| })) | ||
|
|
||
| const CONNECTOR = { | ||
| ...gmailConnectorMeta, | ||
| configFields: gmailConnectorMeta.configFields.filter( | ||
| (field) => field.canonicalParamId === 'label' | ||
| ), | ||
| } | ||
|
|
||
| interface HarnessProps { | ||
| disabled?: boolean | ||
| } | ||
|
|
||
| function Harness({ disabled = false }: HarnessProps) { | ||
| const config = useConnectorConfigFields({ | ||
| connectorConfig: CONNECTOR, | ||
| initialSourceConfig: { labelSelector: ['INBOX', 'IMPORTANT'], label: ['STARRED'] }, | ||
| }) | ||
| return ( | ||
| <ConnectorConfigFields | ||
| connectorConfig={CONNECTOR} | ||
| sourceConfig={config.sourceConfig} | ||
| credentialId={null} | ||
| canonicalGroups={config.canonicalGroups} | ||
| canonicalModes={config.canonicalModes} | ||
| isFieldVisible={config.isFieldVisible} | ||
| onFieldChange={config.handleFieldChange} | ||
| onToggleCanonicalMode={config.toggleCanonicalMode} | ||
| disabled={disabled} | ||
| /> | ||
| ) | ||
| } | ||
|
|
||
| let root: Root | ||
| let container: HTMLDivElement | ||
|
|
||
| beforeEach(() => { | ||
| vi.useFakeTimers() | ||
| ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true | ||
| container = document.createElement('div') | ||
| document.body.appendChild(container) | ||
| root = createRoot(container) | ||
| }) | ||
|
|
||
| afterEach(() => { | ||
| act(() => root.unmount()) | ||
| container.remove() | ||
| vi.useRealTimers() | ||
| }) | ||
|
|
||
| function radio(label: string): HTMLInputElement { | ||
| const input = container.querySelector<HTMLInputElement>( | ||
| `input[type="radio"][aria-label="${label}"]` | ||
| ) | ||
| if (!input) throw new Error(`Missing mode option: ${label}`) | ||
| return input | ||
| } | ||
|
|
||
| describe('connector input mode switch', () => { | ||
| it("preserves each mode's stored values when switching to manual input and back", () => { | ||
| act(() => root.render(<Harness />)) | ||
| expect(radio('Selector').checked).toBe(true) | ||
|
|
||
| act(() => radio('Manual input').click()) | ||
| expect(radio('Manual input').checked).toBe(true) | ||
| expect(container.querySelector<HTMLInputElement>('input:not([type="radio"])')?.value).toBe( | ||
| 'STARRED' | ||
| ) | ||
|
|
||
| act(() => radio('Manual input').click()) | ||
| expect(radio('Manual input').checked).toBe(true) | ||
|
|
||
| act(() => radio('Selector').click()) | ||
| expect(radio('Selector').checked).toBe(true) | ||
| expect(container.querySelector('[data-testid="selector-value"]')?.textContent).toBe( | ||
| 'INBOX,IMPORTANT' | ||
| ) | ||
| }) | ||
|
|
||
| it('keeps the switch outside the field label and ignores clicks on the title', () => { | ||
| act(() => root.render(<Harness />)) | ||
| expect(container.querySelector('[role="radiogroup"]')?.closest('label')).toBeNull() | ||
| act(() => container.querySelector('label')?.click()) | ||
| expect(radio('Selector').checked).toBe(true) | ||
| }) | ||
|
|
||
| it('prevents mode changes while submission disables the fields', () => { | ||
| act(() => root.render(<Harness disabled />)) | ||
| expect(radio('Selector').disabled).toBe(true) | ||
| expect(radio('Manual input').disabled).toBe(true) | ||
| act(() => radio('Manual input').click()) | ||
| expect(radio('Selector').checked).toBe(true) | ||
| }) | ||
|
|
||
| it('keeps focus on the switch through a keyboard mode round trip', () => { | ||
| act(() => root.render(<Harness />)) | ||
| const selector = radio('Selector') | ||
| const manual = radio('Manual input') | ||
|
|
||
| act(() => { | ||
| manual.focus() | ||
| manual.click() | ||
| }) | ||
| expect(radio('Manual input')).toBe(manual) | ||
| expect(manual.checked).toBe(true) | ||
| expect(document.activeElement).toBe(manual) | ||
|
|
||
| act(() => { | ||
| selector.focus() | ||
| selector.click() | ||
| }) | ||
| expect(radio('Selector')).toBe(selector) | ||
| expect(selector.checked).toBe(true) | ||
| expect(document.activeElement).toBe(selector) | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
...ponents/panel/components/editor/components/sub-block/components/canonical-mode-toggle.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { IconSwitch } from '@sim/emcn' | ||
| import { List } from '@sim/emcn/icons' | ||
| import { VariableIcon } from '@/components/icons' | ||
| import type { CanonicalMode } from '@/lib/workflows/subblocks/visibility' | ||
|
|
||
| interface CanonicalModeToggleProps { | ||
| mode: CanonicalMode | ||
| disabled?: boolean | ||
| onToggle?: () => void | ||
| } | ||
|
|
||
| const MODE_OPTIONS = [ | ||
| { value: 'basic', label: 'Selector', icon: List }, | ||
| { value: 'advanced', label: 'Variable', icon: VariableIcon }, | ||
| ] as const | ||
|
|
||
| export function CanonicalModeToggle({ mode, disabled, onToggle }: CanonicalModeToggleProps) { | ||
| return ( | ||
| <IconSwitch | ||
| options={MODE_OPTIONS} | ||
| value={mode} | ||
| onValueChange={() => onToggle?.()} | ||
| disabled={disabled} | ||
| showTooltips | ||
| aria-label='Input mode' | ||
| className='-my-1' | ||
| /> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
.../[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.