diff --git a/.claude/rules/emcn-components.md b/.claude/rules/emcn-components.md
index fd57ee5c00c..af477a7d886 100644
--- a/.claude/rules/emcn-components.md
+++ b/.claude/rules/emcn-components.md
@@ -6,14 +6,14 @@ paths:
# EMCN Components
-Import components, `cn`, and tokens from the `@sim/emcn` barrel; icons come from the `@sim/emcn/icons` subpath, and CSS modules from their file path. Never deep-import other component subpaths. The **chip family** is the platform's primary chrome — always reach for it over the legacy primitives it is progressively replacing (`Input`→`ChipInput`, `Textarea`→`ChipTextarea`, `Modal`→`ChipModal`, `Select`/`Combobox`→`ChipSelect`/`ChipCombobox`/`ChipDropdown`, `Switch`→`ChipSwitch`, date field→`ChipDatePicker`). For context/action menus the canonical control is `DropdownMenu` — the standard menu (not a chip, and never a hand-rolled popover).
+Import components, `cn`, and tokens from the `@sim/emcn` barrel; icons come from the `@sim/emcn/icons` subpath, and CSS modules from their file path. Never deep-import other component subpaths. The **chip family** is the platform's primary chrome — always reach for it over the legacy primitives it is progressively replacing (`Input`→`ChipInput`, `Textarea`→`ChipTextarea`, `Modal`→`ChipModal`, `Select`/`Combobox`→`ChipSelect`/`ChipCombobox`, `Switch`→`ChipSwitch`, date field→`ChipDatePicker`). For context/action menus the canonical control is `DropdownMenu` — the standard menu (not a chip, and never a hand-rolled popover).
## Chip chrome — single source of truth
Never hand-roll the chip pill from raw class strings (they go stale). Compose from the canonical sources:
- **Surface, typography + content tokens:** `chip/chip-chrome.ts` — `chipFilledSurfaceTokens`, `chipFieldSurfaceClass`, `chipFieldTextClass` (text fields and the dropdown search box build on these), plus the chip-content chrome `chipContentGap`, `chipGeometryClass`, `chipContentIconClass`, `chipContentLabelClass`, `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells), and the row-state pair `chipHoverSurfaceClass` / `chipActiveSurfaceClass` (hover vs. selected — mutually exclusive, so a selected row holds its surface through hover; every hand-rolled row imports these rather than restating the literals). All are re-exported from the `@sim/emcn` barrel — no subpath import needed.
-- **Pill geometry:** `chip/chip.tsx` — `chipVariants` (30px tall, `rounded-lg`, `px-2`, icon↔text `gap-1.5`). Every pill-shaped trigger (`ChipDropdown`, `ChipSelect`, `ChipSwitch`) reuses it for visual parity.
+- **Pill geometry:** `chip/chip.tsx` — `chipVariants` (30px tall, `rounded-lg`, `px-2`, icon↔text `gap-1.5`). Every pill-shaped trigger (`ChipSelect`, `ChipDatePicker`) reuses it for visual parity.
Canonical look: normal font-weight (never `font-medium`/`font-semibold`), value text `--text-body`, icons `--text-icon` at `size-[14px]`, placeholder `--text-muted`, `transition-colors`, **no focus ring** (the caret marks focus). Filled surface is `--surface-5` light / `--surface-4` dark with a `--border-1` border.
@@ -25,14 +25,14 @@ The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items
- **`ChipInput`** — single-line text field. `icon`, `endAdornment`, `error`, `inputClassName` (inner ` `); `className` styles the chrome wrapper.
- **`ChipCopyInput`** — the canonical view-only field: a read-only `ChipInput` at full opacity with a trailing copy-to-clipboard button. View-only is a display mode, not a disabled state — reach for it (or `ChipModalField type='copy'`) over a `disabled` (greyed) input for values the user cannot edit.
- **`ChipTextarea`** — multi-line sibling. `error`, `resizable` (off by default), `viewOnly` (read-only at full opacity with the default cursor — the multi-line counterpart of `ChipCopyInput`).
-- **`ChipDropdown`** — pill that opens a menu. Single OR multi-select via the discriminated `multiple` prop (one component, not two). Owns its trailing chevron — no `rightIcon`.
-- **`ChipSelect` / `ChipCombobox`** — `Combobox`-backed pickers with search, groups, multi-select; for richer lists than `ChipDropdown`.
+- **`ChipSelect`** — canonical menu selection. Single selection uses `value`/`onChange`; `multiSelect` uses `multiSelectValues`/`onMultiSelectChange`, checkbox rows, and stays open. Supports groups, search aliases, rich labels/icons, disabled options, optional single-selection checks, and trigger refs. Rich labels require a nonempty `searchTerms` array containing their visible text, followed by any aliases; plain string/number labels are searchable automatically. `showAllOption` clears to an empty selection; without it, use `placeholder` for the empty state. Owns its chevron. `dropdownWidth` accepts `'trigger'`, `'content'`, or pixels; `modal={false}` keeps outside-dialog filters non-modal, while nested dialogs retain their focus lock.
+- **`ChipCombobox`** — shared chip styling over the separate `Combobox` engine for editable values, asynchronous results, and virtualized lists. Plain menu selection, including searchable menus, uses `ChipSelect`.
- **`ChipModal` + `ChipModalField`** — declarative compact modal. The field's `type` (`input` | `email` | `textarea` | `dropdown` | `copy` | `file` | `emails` | `custom`) picks the control and **owns all chrome** — consumers describe intent, never pass `variant`/`className`/`id` to the inner control. `custom` is the escape hatch. **Every body field MUST be a `ChipModalField`** — never hand-roll a field row (raw `
` + hand-rolled `
`/`` title + bare `ChipInput`/`ChipTextarea`). `ChipModalBody` applies `px-2` + `gap-4`; `ChipModalField` adds another `px-2`, so each field lands at effective `px-4`, exactly matching the `px-4` header/footer — a hand-rolled row skips that gutter and sits misaligned at `px-2`. For controls the field doesn't cover (`ChipCombobox`, `ChipSelect`, `DatePicker`, `TimePicker`, `ButtonGroup`, arbitrary JSX), use `type='custom'` with a `title` — it still applies the gutter and renders the canonical `Label`.
-- **`ChipSwitch`** — segmented pill control (built from `chipVariants`).
+- **`ChipButtonGroup` / `ChipSwitch`** — one Radix radio-group implementation with zero-gap segments and `default`/`compact` density. `ChipButtonGroup` accepts compound children; `ChipSwitch` adapts an options array.
- **`ChipTag`** — 20px inline tag/badge (`mono`/`gray`/`invite`), not a pill trigger.
- **`ChipDatePicker`** — chip-styled date field.
- **`ChipTimePicker`** — minute-granular time sibling of `ChipDatePicker`, a `ChipInput` that leniently parses typed input (`9:47`, `947`, `2:05pm`, `14:30`), commits on Enter/blur, and re-renders the canonical `9:47 AM` label.
-- **`DropdownMenu`** — the canonical context/action menu (Radix-backed). Not a chip, but the standard menu for command/action lists; reach for it instead of a hand-rolled popover. Its surface intentionally diverges from the chip pill (`text-small`, `gap-2`) — keep them distinct. For a pill that opens a value picker, use `ChipDropdown`/`ChipSelect` instead.
+- **`DropdownMenu`** — the canonical context/action menu (Radix-backed). Not a chip, but the standard menu for command/action lists; reach for it instead of a hand-rolled popover. Its surface intentionally diverges from the chip pill (`text-small`, `gap-2`) — keep them distinct. For a pill that opens a value picker, use `ChipSelect` instead.
- **`useScrollEdges` + `scrollFadeClass` / `scrollFadeAttributes`** — the canonical scroll-region edge treatment. The hook reports which edges hide content (tracking scroll and resizes; pass the element itself, held in state, when the region mounts after its owner, e.g. inside a Radix portal); the class and attributes fade a fixed 12px band at an active edge only, so a list that fits or sits at its top is never fogged. A floating control over the top edge sets `--scroll-fade-inset` to its height. A region that scrolls sideways (a tab row, a chip strip) uses `useScrollEdges(ref, { axis: 'x' })` with `scrollFadeXClass`; the attributes helper is shared. Any divider beside the region belongs to the neighboring block (`border-b` above, `border-t` below), never to the masked element, and shows only while that edge is active. Never hand-roll a `mask-image` gradient for a scroll region.
- **`OverflowText`** — the canonical single-line overflow treatment for read-only human labels and titles. It owns `min-w-0`, fade-only clipping (never an ellipsis), the conditional 18px edge mask, and the full-value floating tooltip; consumers pass only layout/typography through `className`. `overflowTextClipClass` and `overflowTextFadeClass` are the complete base/faded treatments for the rare component that must own measurement itself; never pair either with `truncate`, `text-ellipsis`, or hover-time mask removal. Use `DropdownMenuItemLabel` for a menu label beside icons, checks, or actions. A non-editable `Combobox` passes the full visual value through `overlayLabel`; the combobox owns the visual overlay's fade and keeps its one accessible tooltip on the interactive layer. Keep ordinary `truncate` only for editable values, code/log/path content, dense or virtualized grids, and rich composite content that cannot supply a plain tooltip label. Multiline copy uses an intentional `line-clamp-*` treatment instead.
@@ -50,7 +50,7 @@ Declare keyboard intent on the action-owning primitive; never add document-level
- **One source of truth for shared chrome.** Compose from `chip-chrome.ts` / `chipVariants`; never duplicate the chrome string.
- **`cn()` for a single state toggle, CVA for genuine multiple variants.** A lone `error` boolean is `cn()`, not a CVA variant.
-- **Discriminated-union props for modes** (e.g. `multiple`, the modal field `type`) instead of near-duplicate components.
+- **Discriminated-union props for modes** (e.g. `multiSelect`, the modal field `type`) instead of near-duplicate components.
- **Delete legacy variants after migration** — don't leave dead paths (this paradigm removed `Input variant='chip'` and `ChipMultiSelect`).
- **Verify CSS vars exist.** An undefined var resolves to `currentColor` (caused a real black-border bug). Align to the canonical tokens: normal weight, `--text-body`, `--text-icon`.
- Use Radix UI primitives for accessibility. Export the component and its `variants` (when using CVA). Document with TSDoc + a usage example.
diff --git a/.cursor/rules/emcn-components.mdc b/.cursor/rules/emcn-components.mdc
index 3f5cd1bf90e..6a70e29d4b3 100644
--- a/.cursor/rules/emcn-components.mdc
+++ b/.cursor/rules/emcn-components.mdc
@@ -7,14 +7,14 @@ globs: ["packages/emcn/**"]
# EMCN Components
-Import components, `cn`, and tokens from the `@sim/emcn` barrel; icons come from the `@sim/emcn/icons` subpath, and CSS modules from their file path. Never deep-import other component subpaths. The **chip family** is the platform's primary chrome — always reach for it over the legacy primitives it is progressively replacing (`Input`→`ChipInput`, `Textarea`→`ChipTextarea`, `Modal`→`ChipModal`, `Select`/`Combobox`→`ChipSelect`/`ChipCombobox`/`ChipDropdown`, `Switch`→`ChipSwitch`, date field→`ChipDatePicker`). For context/action menus the canonical control is `DropdownMenu` — the standard menu (not a chip, and never a hand-rolled popover).
+Import components, `cn`, and tokens from the `@sim/emcn` barrel; icons come from the `@sim/emcn/icons` subpath, and CSS modules from their file path. Never deep-import other component subpaths. The **chip family** is the platform's primary chrome — always reach for it over the legacy primitives it is progressively replacing (`Input`→`ChipInput`, `Textarea`→`ChipTextarea`, `Modal`→`ChipModal`, `Select`/`Combobox`→`ChipSelect`/`ChipCombobox`, `Switch`→`ChipSwitch`, date field→`ChipDatePicker`). For context/action menus the canonical control is `DropdownMenu` — the standard menu (not a chip, and never a hand-rolled popover).
## Chip chrome — single source of truth
Never hand-roll the chip pill from raw class strings (they go stale). Compose from the canonical sources:
- **Surface, typography + content tokens:** `chip/chip-chrome.ts` — `chipFilledSurfaceTokens`, `chipFieldSurfaceClass`, `chipFieldTextClass` (text fields and the dropdown search box build on these), plus the chip-content chrome `chipContentGap`, `chipGeometryClass`, `chipContentIconClass`, `chipContentLabelClass`, `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells), and the row-state pair `chipHoverSurfaceClass` / `chipActiveSurfaceClass` (hover vs. selected — mutually exclusive, so a selected row holds its surface through hover; every hand-rolled row imports these rather than restating the literals). All are re-exported from the `@sim/emcn` barrel — no subpath import needed.
-- **Pill geometry:** `chip/chip.tsx` — `chipVariants` (30px tall, `rounded-lg`, `px-2`, icon↔text `gap-1.5`). Every pill-shaped trigger (`ChipDropdown`, `ChipSelect`, `ChipSwitch`) reuses it for visual parity.
+- **Pill geometry:** `chip/chip.tsx` — `chipVariants` (30px tall, `rounded-lg`, `px-2`, icon↔text `gap-1.5`). Every pill-shaped trigger (`ChipSelect`, `ChipDatePicker`) reuses it for visual parity.
Canonical look: normal font-weight (never `font-medium`/`font-semibold`), value text `--text-body`, icons `--text-icon` at `size-[14px]`, placeholder `--text-muted`, `transition-colors`, **no focus ring** (the caret marks focus). Filled surface is `--surface-5` light / `--surface-4` dark with a `--border-1` border.
@@ -26,14 +26,14 @@ The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items
- **`ChipInput`** — single-line text field. `icon`, `endAdornment`, `error`, `inputClassName` (inner ` `); `className` styles the chrome wrapper.
- **`ChipCopyInput`** — the canonical view-only field: a read-only `ChipInput` at full opacity with a trailing copy-to-clipboard button. View-only is a display mode, not a disabled state — reach for it (or `ChipModalField type='copy'`) over a `disabled` (greyed) input for values the user cannot edit.
- **`ChipTextarea`** — multi-line sibling. `error`, `resizable` (off by default), `viewOnly` (read-only at full opacity with the default cursor — the multi-line counterpart of `ChipCopyInput`).
-- **`ChipDropdown`** — pill that opens a menu. Single OR multi-select via the discriminated `multiple` prop (one component, not two). Owns its trailing chevron — no `rightIcon`.
-- **`ChipSelect` / `ChipCombobox`** — `Combobox`-backed pickers with search, groups, multi-select; for richer lists than `ChipDropdown`.
+- **`ChipSelect`** — canonical menu selection. Single selection uses `value`/`onChange`; `multiSelect` uses `multiSelectValues`/`onMultiSelectChange`, checkbox rows, and stays open. Supports groups, search aliases, rich labels/icons, disabled options, optional single-selection checks, and trigger refs. Rich labels require a nonempty `searchTerms` array containing their visible text, followed by any aliases; plain string/number labels are searchable automatically. `showAllOption` clears to an empty selection; without it, use `placeholder` for the empty state. Owns its chevron. `dropdownWidth` accepts `'trigger'`, `'content'`, or pixels; `modal={false}` keeps outside-dialog filters non-modal, while nested dialogs retain their focus lock.
+- **`ChipCombobox`** — shared chip styling over the separate `Combobox` engine for editable values, asynchronous results, and virtualized lists. Plain menu selection, including searchable menus, uses `ChipSelect`.
- **`ChipModal` + `ChipModalField`** — declarative compact modal. The field's `type` (`input` | `email` | `textarea` | `dropdown` | `copy` | `file` | `emails` | `custom`) picks the control and **owns all chrome** — consumers describe intent, never pass `variant`/`className`/`id` to the inner control. `custom` is the escape hatch. **Every body field MUST be a `ChipModalField`** — never hand-roll a field row (raw `` + hand-rolled `
`/`` title + bare `ChipInput`/`ChipTextarea`). `ChipModalBody` applies `px-2` + `gap-4`; `ChipModalField` adds another `px-2`, so each field lands at effective `px-4`, exactly matching the `px-4` header/footer — a hand-rolled row skips that gutter and sits misaligned at `px-2`. For controls the field doesn't cover (`ChipCombobox`, `ChipSelect`, `DatePicker`, `TimePicker`, `ButtonGroup`, arbitrary JSX), use `type='custom'` with a `title` — it still applies the gutter and renders the canonical `Label`.
-- **`ChipSwitch`** — segmented pill control (built from `chipVariants`).
+- **`ChipButtonGroup` / `ChipSwitch`** — one Radix radio-group implementation with zero-gap segments and `default`/`compact` density. `ChipButtonGroup` accepts compound children; `ChipSwitch` adapts an options array.
- **`ChipTag`** — 20px inline tag/badge (`mono`/`gray`/`invite`), not a pill trigger.
- **`ChipDatePicker`** — chip-styled date field.
- **`ChipTimePicker`** — minute-granular time sibling of `ChipDatePicker`, a `ChipInput` that leniently parses typed input (`9:47`, `947`, `2:05pm`, `14:30`), commits on Enter/blur, and re-renders the canonical `9:47 AM` label.
-- **`DropdownMenu`** — the canonical context/action menu (Radix-backed). Not a chip, but the standard menu for command/action lists; reach for it instead of a hand-rolled popover. Its surface intentionally diverges from the chip pill (`text-small`, `gap-2`) — keep them distinct. For a pill that opens a value picker, use `ChipDropdown`/`ChipSelect` instead.
+- **`DropdownMenu`** — the canonical context/action menu (Radix-backed). Not a chip, but the standard menu for command/action lists; reach for it instead of a hand-rolled popover. Its surface intentionally diverges from the chip pill (`text-small`, `gap-2`) — keep them distinct. For a pill that opens a value picker, use `ChipSelect` instead.
- **`useScrollEdges` + `scrollFadeClass` / `scrollFadeAttributes`** — the canonical scroll-region edge treatment. The hook reports which edges hide content (tracking scroll and resizes; pass the element itself, held in state, when the region mounts after its owner, e.g. inside a Radix portal); the class and attributes fade a fixed 12px band at an active edge only, so a list that fits or sits at its top is never fogged. A floating control over the top edge sets `--scroll-fade-inset` to its height. A region that scrolls sideways (a tab row, a chip strip) uses `useScrollEdges(ref, { axis: 'x' })` with `scrollFadeXClass`; the attributes helper is shared. Any divider beside the region belongs to the neighboring block (`border-b` above, `border-t` below), never to the masked element, and shows only while that edge is active. Never hand-roll a `mask-image` gradient for a scroll region.
- **`OverflowText`** — the canonical single-line overflow treatment for read-only human labels and titles. It owns `min-w-0`, fade-only clipping (never an ellipsis), the conditional 18px edge mask, and the full-value floating tooltip; consumers pass only layout/typography through `className`. `overflowTextClipClass` and `overflowTextFadeClass` are the complete base/faded treatments for the rare component that must own measurement itself; never pair either with `truncate`, `text-ellipsis`, or hover-time mask removal. Use `DropdownMenuItemLabel` for a menu label beside icons, checks, or actions. A non-editable `Combobox` passes the full visual value through `overlayLabel`; the combobox owns the visual overlay's fade and keeps its one accessible tooltip on the interactive layer. Keep ordinary `truncate` only for editable values, code/log/path content, dense or virtualized grids, and rich composite content that cannot supply a plain tooltip label. Multiline copy uses an intentional `line-clamp-*` treatment instead.
@@ -51,7 +51,7 @@ Declare keyboard intent on the action-owning primitive; never add document-level
- **One source of truth for shared chrome.** Compose from `chip-chrome.ts` / `chipVariants`; never duplicate the chrome string.
- **`cn()` for a single state toggle, CVA for genuine multiple variants.** A lone `error` boolean is `cn()`, not a CVA variant.
-- **Discriminated-union props for modes** (e.g. `multiple`, the modal field `type`) instead of near-duplicate components.
+- **Discriminated-union props for modes** (e.g. `multiSelect`, the modal field `type`) instead of near-duplicate components.
- **Delete legacy variants after migration** — don't leave dead paths (this paradigm removed `Input variant='chip'` and `ChipMultiSelect`).
- **Verify CSS vars exist.** An undefined var resolves to `currentColor` (caused a real black-border bug). Align to the canonical tokens: normal weight, `--text-body`, `--text-icon`.
- Use Radix UI primitives for accessibility. Export the component and its `variants` (when using CVA). Document with TSDoc + a usage example.
diff --git a/CLAUDE.md b/CLAUDE.md
index 29ca6ff4ad1..9187f83671e 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -416,7 +416,7 @@ On chip components (see "EMCN Components"), drive chrome through PROPS, not `cla
Import components, `cn`, and tokens from the `@sim/emcn` barrel; icons come from the `@sim/emcn/icons` subpath, and CSS modules from their file path. Never deep-import other component subpaths. Use CVA only when 2+ genuine variants exist; otherwise plain `cn()`.
-The chip family is the canonical UI chrome and is progressively replacing the legacy EMCN primitives — always reach for the chip equivalent: `ChipInput` over `Input`, `ChipTextarea` over `Textarea`, `ChipModal`/`ChipModalField` over `Modal`, `ChipSelect`/`ChipCombobox` (searchable) or `ChipDropdown` (simple menu-select) over `Select`/`Combobox`, `ChipSwitch` over `Switch`, `ChipDatePicker` over a raw date field, `Chip`/`ChipLink` for pill buttons/links, `ChipTag` for inline tags/badges. For context/action menus the canonical control is `DropdownMenu` (not a chip, but the standard menu — not a hand-rolled popover). Components OWN their chrome (single source of truth) — consumers pass props, not class overrides. Authoring rules in `.claude/rules/emcn-components.md`; consumer rules in `.claude/rules/sim-styling.md`.
+The chip family is the canonical UI chrome and is progressively replacing the legacy EMCN primitives — always reach for the chip equivalent: `ChipInput` over `Input`, `ChipTextarea` over `Textarea`, `ChipModal`/`ChipModalField` over `Modal`, `ChipSelect` (menu selection, including search) or `ChipCombobox` (editable/async/virtualized selection) over `Select`/`Combobox`, `ChipSwitch` over `Switch`, `ChipDatePicker` over a raw date field, `Chip`/`ChipLink` for pill buttons/links, `ChipTag` for inline tags/badges. For context/action menus the canonical control is `DropdownMenu` (not a chip, but the standard menu — not a hand-rolled popover). Components OWN their chrome (single source of truth) — consumers pass props, not class overrides. Authoring rules in `.claude/rules/emcn-components.md`; consumer rules in `.claude/rules/sim-styling.md`.
Inside a `ChipModalBody`, EVERY labeled field MUST be a `ChipModalField` — never hand-roll a field row (a raw `` + a hand-rolled `
`/`` title + a bare `ChipInput`/`ChipTextarea`). `ChipModalBody` applies `px-2` + `gap-4`; `ChipModalField` adds ANOTHER `px-2`, so each field lands at effective `px-4`, exactly matching `ChipModalHeader`/`ChipModalFooter` (`px-4`). Hand-rolled rows skip the field's gutter and sit at `px-2`, visibly misaligned with the header/footer. For controls `ChipModalField` does not cover (`ChipCombobox`, `ChipSelect`, `DatePicker`, `TimePicker`, `ButtonGroup`, arbitrary JSX), use `ChipModalField type='custom'` with a `title` — it still applies the `px-2` gutter and renders the canonical `Label`. Drive intent via props (`title`/`value`/`onChange`/`error`/`hint`/`required`/`flush`); never pass `variant`/`className`/`id` to the inner control, and never add a body-level wrapper `` with a custom `gap-*` that fights `ChipModalBody`'s `gap-4`.
@@ -426,7 +426,7 @@ Principles when building or migrating shared UI:
- One canonical source of truth for shared chrome — compose it, never re-derive it per consumer.
- Props-driven API over `className` overrides — reaching for `className` to change chrome is a smell; expose a prop instead.
-- Discriminated-union props for modes (e.g. `ChipDropdown multiple`) over near-duplicate components.
+- Discriminated-union props for modes (e.g. `ChipSelect multiSelect`) over near-duplicate components.
- Delete legacy variants/components after migration — no parallel paths left behind.
- Plain `cn()` for a single error/state toggle; CVA only for genuinely multiple variants.
- Align consumers to the canonical defaults — normal weight, `--text-body` text, `--text-icon` icons.
diff --git a/apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx b/apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx
index 8710f405311..248d24644ee 100644
--- a/apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx
+++ b/apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx
@@ -1,7 +1,7 @@
'use client'
import { useEffect, useState } from 'react'
-import { cn, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label } from '@sim/emcn'
+import { ChipInput, cn, InputOTP, InputOTPGroup, InputOTPSlot, Label } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { toError } from '@sim/utils/errors'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
@@ -134,7 +134,7 @@ export default function EmailAuth({ identifier }: EmailAuthProps) {
Email
-
{hasEmailError && (
diff --git a/apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx b/apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx
index 378fba15cdc..df723907a00 100644
--- a/apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx
+++ b/apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx
@@ -1,11 +1,10 @@
'use client'
import { useState } from 'react'
-import { cn, Input, Label } from '@sim/emcn'
-import { Eye, EyeOff } from '@sim/emcn/icons'
+import { cn, Label } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { toError } from '@sim/utils/errors'
-import { AuthSubmitButton } from '@/app/(auth)/components'
+import { AuthSubmitButton, PasswordInput } from '@/app/(auth)/components'
import { useChatPasswordAuth } from '@/hooks/queries/chats'
const logger = createLogger('PasswordAuth')
@@ -16,7 +15,6 @@ interface PasswordAuthProps {
export default function PasswordAuth({ identifier }: PasswordAuthProps) {
const [password, setPassword] = useState('')
- const [showPassword, setShowPassword] = useState(false)
const [passwordErrors, setPasswordErrors] = useState([])
const hasPasswordError = passwordErrors.length > 0
const authenticate = useChatPasswordAuth(identifier)
@@ -67,37 +65,19 @@ export default function PasswordAuth({ identifier }: PasswordAuthProps) {
Password
-
-
- setShowPassword(!showPassword)}
- className='-translate-y-1/2 absolute top-1/2 right-3 text-[var(--text-muted)] hover-hover:text-[var(--text-primary)]'
- aria-label={showPassword ? 'Hide password' : 'Show password'}
- >
- {showPassword ? (
-
- ) : (
-
- )}
-
-
+
0 && (
{uploadErrors.map((error, idx) => (
-
+
{error}
))}
diff --git a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/enterprise-menu-preview/enterprise-menu-preview.tsx b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/enterprise-menu-preview/enterprise-menu-preview.tsx
index 5d187bb7345..0d3ca4fb9d1 100644
--- a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/enterprise-menu-preview/enterprise-menu-preview.tsx
+++ b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/enterprise-menu-preview/enterprise-menu-preview.tsx
@@ -1,4 +1,4 @@
-import { Chip, ChipDropdown, cn } from '@sim/emcn'
+import { Chip, ChipSelect, cn } from '@sim/emcn'
import { Building, MoreHorizontal, Plus } from '@sim/emcn/icons'
import { MenuPreviewFrame } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/menu-preview-frame'
import { MenuPreviewHeader } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/menu-preview-header/menu-preview-header'
@@ -47,11 +47,14 @@ export function EnterpriseMenuPreview({ layout = 'menu' }: EnterpriseMenuPreview
image={null}
status=''
roleControl={
-
}
menu={ }
diff --git a/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx b/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx
index 534fa557cdc..787a852a5e1 100644
--- a/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx
+++ b/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx
@@ -2,7 +2,7 @@
import { type ReactNode, useId, useRef, useState } from 'react'
import { Turnstile, type TurnstileInstance } from '@marsidev/react-turnstile'
-import { Chip, ChipDropdown, ChipInput, ChipTextarea, Label } from '@sim/emcn'
+import { Chip, ChipInput, ChipSelect, ChipTextarea, Label } from '@sim/emcn'
import { Check } from '@sim/emcn/icons'
import { toError } from '@sim/utils/errors'
import {
@@ -286,7 +286,10 @@ export function ContactForm() {
/>
-
-
- option.value === (organizationRoles[member.email] ?? member.role))?.label ?? 'Owner'}`}
value={organizationRoles[member.email] ?? member.role}
options={
@@ -76,7 +79,7 @@ export function EnterpriseMembersPreview() {
setOrganizationRoles((roles) => ({ ...roles, [member.email]: role }))
}
disabled={member.role === 'owner'}
- matchTriggerWidth={false}
+ dropdownWidth='content'
/>
}
/>
@@ -95,7 +98,10 @@ export function EnterpriseMembersPreview() {
image={null}
status=''
roleControl={
- option.value === (workspaceRoles[member.email] ?? (member.role === 'owner' ? 'admin' : 'write')))?.label}`}
value={
workspaceRoles[member.email] ?? (member.role === 'owner' ? 'admin' : 'write')
@@ -105,7 +111,7 @@ export function EnterpriseMembersPreview() {
setWorkspaceRoles((roles) => ({ ...roles, [member.email]: role }))
}
disabled={member.role === 'owner'}
- matchTriggerWidth={false}
+ dropdownWidth='content'
/>
}
/>
diff --git a/apps/sim/app/(landing)/logs/components/log-history-preview/log-history-preview.tsx b/apps/sim/app/(landing)/logs/components/log-history-preview/log-history-preview.tsx
index 2e1dba49f5f..af9574dcb6f 100644
--- a/apps/sim/app/(landing)/logs/components/log-history-preview/log-history-preview.tsx
+++ b/apps/sim/app/(landing)/logs/components/log-history-preview/log-history-preview.tsx
@@ -1,7 +1,7 @@
'use client'
import { useEffect, useId, useRef, useState } from 'react'
-import { Badge, Chip, ChipDropdown, ChipInput } from '@sim/emcn'
+import { Badge, Chip, ChipInput, ChipSelect } from '@sim/emcn'
import { Library, ListFilter, Search, X } from '@sim/emcn/icons'
import {
MenuPreviewHeader,
@@ -109,13 +109,16 @@ export function LogHistoryPreview() {
onChange={(event) => setQuery(event.target.value)}
className='mr-auto min-w-0 flex-1'
/>
- option.value === filter)?.label ?? filter}`}
value={filter}
options={FILTERS}
onChange={setFilter}
- matchTriggerWidth={false}
+ dropdownWidth='content'
/>
diff --git a/apps/sim/app/(landing)/tables/components/tables-records-preview/components/lead-record-detail/lead-record-detail.tsx b/apps/sim/app/(landing)/tables/components/tables-records-preview/components/lead-record-detail/lead-record-detail.tsx
index 9eb6378eb52..7236265ea4b 100644
--- a/apps/sim/app/(landing)/tables/components/tables-records-preview/components/lead-record-detail/lead-record-detail.tsx
+++ b/apps/sim/app/(landing)/tables/components/tables-records-preview/components/lead-record-detail/lead-record-detail.tsx
@@ -1,11 +1,11 @@
import { useState } from 'react'
import {
- ChipDropdown,
ChipModal,
ChipModalBody,
ChipModalField,
ChipModalFooter,
ChipModalHeader,
+ ChipSelect,
} from '@sim/emcn'
import { TagIcon, TypeNumber, TypeText } from '@sim/emcn/icons'
import type { LeadRecord } from '@/app/(landing)/tables/components/tables-records-preview/data'
@@ -65,7 +65,11 @@ export function LeadRecordDetail({ record, onClose, onSave }: LeadRecordDetailPr
}
>
-
(null)
const handleAuthenticate = async () => {
@@ -49,35 +47,22 @@ export function PublicFileAuth({ token }: PublicFileAuthProps) {
>
Password
-
- {
- setPassword(e.target.value)
- setError(null)
- }}
- className={cn(
- 'pr-10',
- error && 'border-[var(--text-error)] focus:border-[var(--text-error)]'
- )}
- />
- setShowPassword(!showPassword)}
- className='-translate-y-1/2 absolute top-1/2 right-3 text-[var(--text-muted)] hover:text-[var(--text-primary)]'
- aria-label={showPassword ? 'Hide password' : 'Show password'}
- >
- {showPassword ? : }
-
-
+
{
+ setPassword(e.target.value)
+ setError(null)
+ }}
+ className='h-[34px]'
+ error={Boolean(error)}
+ />
{error ? {error}
: null}
diff --git a/apps/sim/app/f/[token]/public-file-email-auth.tsx b/apps/sim/app/f/[token]/public-file-email-auth.tsx
index 71a5c564492..a401c748e3f 100644
--- a/apps/sim/app/f/[token]/public-file-email-auth.tsx
+++ b/apps/sim/app/f/[token]/public-file-email-auth.tsx
@@ -1,7 +1,7 @@
'use client'
import { useEffect, useState } from 'react'
-import { cn, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label } from '@sim/emcn'
+import { ChipInput, cn, InputOTP, InputOTPGroup, InputOTPSlot, Label } from '@sim/emcn'
import { getErrorMessage } from '@sim/utils/errors'
import { normalizeEmail } from '@sim/utils/string'
import { useRouter } from 'next/navigation'
@@ -90,7 +90,7 @@ export function PublicFileEmailAuth({ token }: PublicFileEmailAuthProps) {
>
Email
-
{error ?
{error}
: null}
diff --git a/apps/sim/app/f/[token]/public-file-sso-auth.tsx b/apps/sim/app/f/[token]/public-file-sso-auth.tsx
index 3e73581f715..26ca1961483 100644
--- a/apps/sim/app/f/[token]/public-file-sso-auth.tsx
+++ b/apps/sim/app/f/[token]/public-file-sso-auth.tsx
@@ -1,7 +1,7 @@
'use client'
import { useState } from 'react'
-import { cn, Input, Label } from '@sim/emcn'
+import { ChipInput, Label } from '@sim/emcn'
import { getErrorMessage } from '@sim/utils/errors'
import { normalizeEmail } from '@sim/utils/string'
import { useRouter } from 'next/navigation'
@@ -68,7 +68,7 @@ export function PublicFileSSOAuth({ token }: PublicFileSSOAuthProps) {
>
Work Email
-
{error ?
{error}
: null}
diff --git a/apps/sim/app/playground/page.tsx b/apps/sim/app/playground/page.tsx
index fa5a295eacd..b014041848c 100644
--- a/apps/sim/app/playground/page.tsx
+++ b/apps/sim/app/playground/page.tsx
@@ -307,12 +307,6 @@ export default function PlaygroundPage() {
Destructive
-
- Secondary
-
-
- Tertiary
-
Ghost
@@ -344,14 +338,8 @@ export default function PlaygroundPage() {
JavaScript
-
-
- Option 1
- Option 2
-
-
-
-
+
+
Option 1
Option 2
@@ -410,9 +398,6 @@ export default function PlaygroundPage() {
Amber
-
- Teal
-
Cyan
@@ -425,7 +410,6 @@ export default function PlaygroundPage() {
Small
Medium
- Large
@@ -525,10 +509,7 @@ export default function PlaygroundPage() {
Medium (16px)
-
-
- Large (20px)
-
+
@@ -879,7 +860,7 @@ export default function PlaygroundPage() {
- Inverted Popover
+ Inverted Popover
Item 1
diff --git a/apps/sim/app/workspace/[workspaceId]/components/invite-modal/invite-modal.test.tsx b/apps/sim/app/workspace/[workspaceId]/components/invite-modal/invite-modal.test.tsx
index f27e865a6a1..ac53da16d2f 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/invite-modal/invite-modal.test.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/invite-modal/invite-modal.test.tsx
@@ -21,7 +21,7 @@ const { hostContext, mockUseOrganizationBilling, mockUseAdminWorkspaces, mockMut
)
vi.mock('@sim/emcn', () => ({
- ChipDropdown: () =>
,
+ ChipSelect: () =>
,
ChipModal: ({ children }: { children: ReactNode }) => {children}
,
ChipModalBody: ({ children }: { children: ReactNode }) => {children}
,
ChipModalError: ({ children }: { children: ReactNode }) => {children}
,
diff --git a/apps/sim/app/workspace/[workspaceId]/components/invite-modal/invite-modal.tsx b/apps/sim/app/workspace/[workspaceId]/components/invite-modal/invite-modal.tsx
index 090a9096847..2347899cd2f 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/invite-modal/invite-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/invite-modal/invite-modal.tsx
@@ -2,14 +2,14 @@
import { useCallback, useMemo, useState } from 'react'
import {
- ChipDropdown,
- type ChipDropdownOption,
ChipModal,
ChipModalBody,
ChipModalError,
ChipModalField,
ChipModalFooter,
ChipModalHeader,
+ ChipSelect,
+ type ChipSelectOption,
toast,
} from '@sim/emcn'
import { createLogger } from '@sim/logger'
@@ -161,7 +161,7 @@ export function InviteModal({
{ enabled: open && isOrganizationInvite && !organizationOnly }
)
- const workspaceOptions = useMemo(() => {
+ const workspaceOptions = useMemo(() => {
if (!isOrganizationInvite) {
return workspaceId ? [{ value: workspaceId, label: workspaceName ?? 'This workspace' }] : []
}
@@ -317,12 +317,15 @@ export function InviteModal({
{!organizationOnly && (
<>
-
-
-
- {exceedsLimit
- ? `${selectedCount} selected · select ${maxSelectable} or fewer`
- : `${selectedCount} selected`}
-
-
- {onDownload && (
-
- )}
- {onMove && moveOptions && (
-
-
-
-
-
-
-
-
-
- Move
-
-
- {renderMoveOptions(moveOptions, onMove)}
-
-
- )}
- {onDelete && (
-
- )}
-
-
+
+ {exceedsLimit
+ ? `${selectedCount} selected · select ${maxSelectable} or fewer`
+ : `${selectedCount} selected`}
+
+ }
+ >
+ {onDownload && (
+
+ )}
+ {onMove && moveOptions && (
+
+
+
+
+
+
+
+
+
+ Move
+
+
+ {renderMoveOptions(moveOptions, onMove)}
+
+
+ )}
+ {onDelete && (
+
+ )}
+
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx
index af31043f573..aa14ffd07ee 100644
--- a/apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx
@@ -1,7 +1,7 @@
'use client'
import { useEffect, useMemo, useRef, useState } from 'react'
-import { Chip, ChipDropdown, ChipLink, cn } from '@sim/emcn'
+import { Chip, ChipLink, ChipSelect, cn } from '@sim/emcn'
import { ArrowLeft, Plus } from '@sim/emcn/icons'
import { useRouter } from 'next/navigation'
import { useQueryState } from 'nuqs'
@@ -193,14 +193,16 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration
) : oauthService ? (
connectOptions.length > 1 ? (
-
) : oauthAvailable ? (
setOAuthOpen(true)}>
diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx
index 68ab142e2d8..627d13edf1a 100644
--- a/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx
@@ -4,10 +4,10 @@ import { type ReactNode, useEffect, useMemo, useState } from 'react'
import {
Button,
Chip,
- ChipDropdown,
- type ChipDropdownOption,
ChipInput,
ChipModalField,
+ ChipSelect,
+ type ChipSelectOption,
SecretInput,
Wizard,
} from '@sim/emcn'
@@ -50,7 +50,7 @@ const CUSTOM_BOT_CAPABILITIES = [
const ALL_CAPABILITIES = new Set(CUSTOM_BOT_CAPABILITIES.map((capability) => capability.id))
-const CAPABILITY_OPTIONS: ChipDropdownOption[] = CUSTOM_BOT_CAPABILITIES.map((capability) => ({
+const CAPABILITY_OPTIONS: ChipSelectOption[] = CUSTOM_BOT_CAPABILITIES.map((capability) => ({
value: capability.id,
label: capability.label,
}))
@@ -404,13 +404,16 @@ function StepConfigure({
: undefined
}
>
-
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx
index 13819242fb2..3f8f9bec003 100644
--- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx
@@ -701,7 +701,7 @@ export function DocumentTagsModal({
!kbTagDefinitions.find(
(def) => def.displayName.toLowerCase() === editTagForm.displayName.toLowerCase()
) && (
-
+
Maximum tag definitions reached. You can still use existing tag definitions,
but cannot create new ones.
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx
index 9330027ea58..683c692cb15 100644
--- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx
@@ -7,12 +7,12 @@ import {
ChipConfirmModal,
type ChipConfirmTextSegment,
ChipDatePicker,
- ChipDropdown,
- type ChipDropdownOption,
ChipInput,
ChipModal,
ChipModalBody,
ChipModalHeader,
+ ChipSelect,
+ type ChipSelectOption,
cellIconNodeClass,
chipContentGap,
chipContentLabelClass,
@@ -162,7 +162,7 @@ const PROCESSING_POLL_INTERVAL_MS = 3000
/** Slower cadence while only a connector sync is running: rows arrive in batches. */
const CONNECTOR_SYNC_DOCUMENT_POLL_INTERVAL_MS = 5000
-const STATUS_FILTER_OPTIONS: ChipDropdownOption[] = [
+const STATUS_FILTER_OPTIONS: ChipSelectOption[] = [
{ value: 'all', label: 'All' },
{ value: 'enabled', label: 'Enabled' },
{ value: 'disabled', label: 'Disabled' },
@@ -1096,7 +1096,10 @@ export function KnowledgeBase({
)}
-
{
@@ -1731,7 +1734,7 @@ function TagFilterValueControl({ entry, onChange }: TagFilterValueControlProps)
function TagFilterSection({ tagDefinitions, entries, onChange }: TagFilterSectionProps) {
const activeCount = entries.filter((f) => f.tagSlot && f.value.trim()).length
- const tagOptions: ChipDropdownOption[] = tagDefinitions.map((t) => ({
+ const tagOptions: ChipSelectOption[] = tagDefinitions.map((t) => ({
value: t.displayName,
label: t.displayName,
}))
@@ -1801,7 +1804,7 @@ function TagFilterSection({ tagDefinitions, entries, onChange }: TagFilterSectio
>
{filtersToShow.map((entry, index) => {
const operators = getOperatorsForFieldType(entry.fieldType)
- const operatorOptions: ChipDropdownOption[] = operators.map((op) => ({
+ const operatorOptions: ChipSelectOption[] = operators.map((op) => ({
value: op.value,
label: op.label,
}))
@@ -1818,24 +1821,29 @@ function TagFilterSection({ tagDefinitions, entries, onChange }: TagFilterSectio
)}
- handleTagChange(entry.id, value)}
placeholder='Select tag'
align='start'
- matchTriggerWidth={false}
+ dropdownWidth='content'
contentClassName='max-h-[240px] overflow-y-auto'
- className='max-w-[150px]'
+ className='w-auto max-w-[150px]'
/>
{entry.tagSlot && (
- updateEntry(entry.id, { operator: value, valueTo: '' })}
placeholder='Operator'
align='start'
- matchTriggerWidth={false}
+ dropdownWidth='content'
/>
)}
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/action-bar/action-bar.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/action-bar/action-bar.tsx
index 01dbfaead48..3bd0cab96d4 100644
--- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/action-bar/action-bar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/action-bar/action-bar.tsx
@@ -1,4 +1,4 @@
-import { BulkActionButton, cn, Tooltip } from '@sim/emcn'
+import { BulkActionBar, BulkActionButton, cn, Tooltip } from '@sim/emcn'
import { Ban, Circle, Trash } from '@sim/emcn/icons'
import { domAnimation, LazyMotion, m } from 'framer-motion'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
@@ -53,70 +53,70 @@ export function ActionBar({
transition={{ duration: 0.2 }}
className={cn('-translate-x-1/2 fixed bottom-6 left-1/2 z-[var(--z-dropdown)]', className)}
>
-
-
- {isAllSelected ? totalCount : selectedCount} selected
- {showSelectAllOption && (
- <>
- {' · '}
-
- Select all
-
- >
- )}
- {isAllSelected && onClearSelectAll && (
- <>
- {' · '}
-
- Clear
-
- >
- )}
-
+
+ {isAllSelected ? totalCount : selectedCount} selected
+ {showSelectAllOption && (
+ <>
+ {' · '}
+
+ Select all
+
+ >
+ )}
+ {isAllSelected && onClearSelectAll && (
+ <>
+ {' · '}
+
+ Clear
+
+ >
+ )}
+ >
+ }
+ >
+ {showEnableButton && (
+
+
+
+
+
+
+ Enable
+
+ )}
-
- {showEnableButton && (
-
-
-
-
-
-
- Enable
-
- )}
+ {showDisableButton && (
+
+
+
+
+
+
+ Disable
+
+ )}
- {showDisableButton && (
-
-
-
-
-
-
- Disable
-
- )}
-
- {onDelete && canEdit && (
-
-
-
-
-
-
- Delete
-
- )}
-
-
+ {onDelete && canEdit && (
+
+
+
+
+
+
+ Delete
+
+ )}
+
)
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-access-field/connector-access-field.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-access-field/connector-access-field.tsx
index bd2cc556aa0..cefe21c6f32 100644
--- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-access-field/connector-access-field.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-access-field/connector-access-field.tsx
@@ -5,9 +5,9 @@ import {
ChipButtonGroup,
ChipButtonGroupItem,
ChipCombobox,
- ChipDropdown,
ChipLink,
ChipModalField,
+ ChipSelect,
type ComboboxOption,
Tooltip,
} from '@sim/emcn'
@@ -170,12 +170,15 @@ export function ConnectorAccessField({
- ({ value: mode, label }))}
disabled
- className='pointer-events-none w-fit'
+ className='pointer-events-none w-fit max-w-none'
/>
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-documents/connector-document-status-filter.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-documents/connector-document-status-filter.tsx
index 749b08d852c..6a5843c27f1 100644
--- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-documents/connector-document-status-filter.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-documents/connector-document-status-filter.tsx
@@ -1,6 +1,6 @@
'use client'
-import { ChipDropdown } from '@sim/emcn'
+import { ChipSelect } from '@sim/emcn'
import type {
ConnectorDocumentFilter,
ConnectorDocumentsData,
@@ -20,14 +20,17 @@ export function ConnectorDocumentStatusFilter({
isLoading,
}: ConnectorDocumentStatusFilterProps) {
return (
- {
if (value === 'active' || value === 'excluded' || value === 'failed' || value === 'skipped')
onFilterChange(value)
}}
- matchTriggerWidth={false}
+ dropdownWidth='content'
options={[
{ value: 'active', label: isLoading ? 'Included' : `Included (${counts.active})` },
{ value: 'excluded', label: isLoading ? 'Excluded' : `Excluded (${counts.excluded})` },
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx
index ccda6b899a9..994b3d820d9 100644
--- a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx
@@ -1,8 +1,8 @@
'use client'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import type { ChipDropdownOption } from '@sim/emcn'
-import { Button, ChipConfirmModal, ChipDropdown, Tooltip, toast } from '@sim/emcn'
+import type { ChipSelectOption } from '@sim/emcn'
+import { Button, ChipConfirmModal, ChipSelect, Tooltip, toast } from '@sim/emcn'
import { Database, FolderPlus, Pencil, Plus, Trash } from '@sim/emcn/icons'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
@@ -127,13 +127,13 @@ const SEARCH_COLUMNS: ResourceColumn[] = [...COLUMNS, FOLDER_LOCATION_COLUMN]
const KNOWLEDGE_BASE_ICON =
-const CONNECTOR_FILTER_OPTIONS: ChipDropdownOption[] = [
+const CONNECTOR_FILTER_OPTIONS: ChipSelectOption[] = [
{ value: 'all', label: 'All' },
{ value: 'connected', label: 'With connectors' },
{ value: 'unconnected', label: 'Without connectors' },
]
-const CONTENT_FILTER_OPTIONS: ChipDropdownOption[] = [
+const CONTENT_FILTER_OPTIONS: ChipSelectOption[] = [
{ value: 'all', label: 'All' },
{ value: 'has-docs', label: 'Has documents' },
{ value: 'empty', label: 'Empty' },
@@ -1320,7 +1320,7 @@ function KnowledgeContent() {
[activeSort, setListSort, clearListSort]
)
- const memberOptions: ChipDropdownOption[] = useMemo(
+ const memberOptions: ChipSelectOption[] = useMemo(
() =>
(members ?? []).map((m) => ({
value: m.userId,
@@ -1346,7 +1346,10 @@ function KnowledgeContent() {
)}
- setConnectorFilter(value === 'all' ? [] : [value])}
@@ -1367,7 +1370,10 @@ function KnowledgeContent() {
)}
-
setContentFilter(value === 'all' ? [] : [value])}
@@ -1389,12 +1395,16 @@ function KnowledgeContent() {
)}
-
-
setDraft((prev) => ({ ...prev, language: language as SandboxLanguage }))
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.test.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.test.tsx
index dd3249fab67..0fc1ce4fa3e 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.test.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.test.tsx
@@ -33,7 +33,6 @@ const { recordChipSelectProps } = vi.hoisted(() => ({
vi.mock('@sim/emcn', () => ({
Chip: ({ children }: { children: ReactNode }) => {children} ,
- ChipDropdown: () => null,
ChipInput: () => null,
ChipSelect: (props: MockChipSelectProps) => {
recordChipSelectProps(props)
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.tsx
index 51e6ff33c52..595ca1e45c7 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.tsx
@@ -1,7 +1,7 @@
'use client'
import { useMemo, useState } from 'react'
-import { Chip, ChipDropdown, ChipInput, ChipTextarea, cn } from '@sim/emcn'
+import { Chip, ChipInput, ChipSelect, ChipTextarea, cn } from '@sim/emcn'
import type { SandboxDependencyIssue } from '@/lib/api/contracts/sandboxes'
import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu'
import { ManagedCliSelect } from '@/app/workspace/[workspaceId]/settings/components/sandboxes/components/managed-cli-select'
@@ -67,7 +67,11 @@ export function SandboxEditor({
/>
- onChange({ ...draft, language: language as SandboxLanguage })}
options={LANGUAGE_OPTIONS.map((option) => ({
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/components/organization-member-lists/organization-member-lists.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/components/organization-member-lists/organization-member-lists.tsx
index 42ca096a093..43e5f9d25a5 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/components/organization-member-lists/organization-member-lists.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/team-management/components/organization-member-lists/organization-member-lists.tsx
@@ -1,7 +1,7 @@
'use client'
import { useMemo, useState } from 'react'
-import { ChipDropdown, ChipTag, toast } from '@sim/emcn'
+import { ChipSelect, ChipTag, toast } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { isOrgAdminRole } from '@sim/platform-authz/predicates'
import { getErrorMessage } from '@sim/utils/errors'
@@ -138,7 +138,10 @@ export function OrganizationMemberLists({
}
roleControl={
editable ? (
-
updateMemberRole
@@ -150,7 +153,7 @@ export function OrganizationMemberLists({
.catch((error) => logger.error('Failed to update member role', { error }))
}
options={ORG_ROLE_OPTIONS}
- matchTriggerWidth={false}
+ dropdownWidth='content'
disabled={updateMemberRole.isPending}
/>
) : (
@@ -263,7 +266,10 @@ export function OrganizationMemberLists({
{isExternal ? 'External' : invitation.role === 'admin' ? 'Admin' : 'Member'}
) : (
-
updateInvitation
@@ -275,7 +281,7 @@ export function OrganizationMemberLists({
.catch((error) => logger.error('Failed to update invitation role', { error }))
}
options={ORG_ROLE_OPTIONS}
- matchTriggerWidth={false}
+ dropdownWidth='content'
disabled={updateInvitation.isPending}
/>
)
@@ -315,7 +321,10 @@ export function OrganizationMemberLists({
}
roleControl={
-
updatePermissions
@@ -329,7 +338,7 @@ export function OrganizationMemberLists({
)
}
options={WORKSPACE_ROLE_OPTIONS}
- matchTriggerWidth={false}
+ dropdownWidth='content'
disabled={disabled}
/>
@@ -364,7 +373,10 @@ export function OrganizationMemberLists({
access: RosterWorkspaceAccess
) => {
const roleControl = (
-
updateInvitation
@@ -376,7 +388,7 @@ export function OrganizationMemberLists({
.catch((error) => logger.error('Failed to update invitation grant', { error }))
}
options={WORKSPACE_ROLE_OPTIONS}
- matchTriggerWidth={false}
+ dropdownWidth='content'
disabled={!canManage || updateInvitation.isPending}
/>
)
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/teammates/teammates.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/teammates/teammates.tsx
index de8294ee6c5..133c90c68b9 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/teammates/teammates.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/teammates/teammates.tsx
@@ -1,7 +1,7 @@
'use client'
import { useCallback, useMemo, useState } from 'react'
-import { ChipDropdown, Plus, toast } from '@sim/emcn'
+import { ChipSelect, Plus, toast } from '@sim/emcn'
import { getErrorMessage } from '@sim/utils/errors'
import { formatDate } from '@sim/utils/formatting'
import { useQueryClient } from '@tanstack/react-query'
@@ -238,11 +238,14 @@ export function Teammates() {
status={teammate.status}
roleControl={
- handleRoleChange(teammate, role as WorkspacePermission)}
options={ROLE_OPTIONS}
- matchTriggerWidth={false}
+ dropdownWidth='content'
disabled={
teammate.isPending ||
!canManage ||
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/select-field/select-value-editor.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/select-field/select-value-editor.tsx
index cdd89e8e821..44cf3ae11bb 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/select-field/select-value-editor.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/select-field/select-value-editor.tsx
@@ -1,7 +1,7 @@
'use client'
import { useMemo } from 'react'
-import { ChipDropdown } from '@sim/emcn'
+import { ChipSelect } from '@sim/emcn'
import type { ColumnDefinition } from '@/lib/table'
import { SelectPill, selectedOptionIds } from './select-pill'
@@ -18,7 +18,7 @@ const CLEAR_VALUE = ''
/**
* Option picker for `select`/`multiselect` cells in a form context (the row
- * modal) — a `ChipDropdown` pill that lists each option as its colored pill and
+ * modal) — a `ChipSelect` pill that lists each option as its colored pill and
* writes option ids back through `onChange`. Inline grid editing uses a bare
* `DropdownMenu` instead (see `InlineSelectEditor`).
*/
@@ -35,31 +35,30 @@ export function SelectValueEditor({
(column.options ?? []).map((option) => ({
value: option.id,
label: ,
+ searchTerms: [option.name] as const,
})),
[column.options]
)
if (isMulti) {
return (
- {
+ onMultiSelectChange={(ids) => {
if (column.required && ids.length === 0) return
onChange(ids)
}}
options={options}
showAllOption={false}
- // In multiple mode ChipDropdown ignores `placeholder` and renders
- // `allLabel` when nothing is selected — which would read as if every
- // option were chosen. There is no "All" entry here, so this is the
- // empty label.
- allLabel='Select options'
align={align}
fullWidth={fullWidth}
- matchTriggerWidth={false}
+ dropdownWidth='content'
/>
)
}
@@ -69,19 +68,26 @@ export function SelectValueEditor({
const singleOptions = column.required
? options
: [
- { value: CLEAR_VALUE, label: None },
+ {
+ value: CLEAR_VALUE,
+ label: None ,
+ searchTerms: ['None'] as const,
+ },
...options,
]
return (
- onChange(id === CLEAR_VALUE ? null : id)}
options={singleOptions}
placeholder='Select an option'
align={align}
fullWidth={fullWidth}
- matchTriggerWidth={false}
+ dropdownWidth='content'
/>
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-action-bar/table-action-bar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-action-bar/table-action-bar.tsx
index b04257ae755..9922997c840 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-action-bar/table-action-bar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-action-bar/table-action-bar.tsx
@@ -1,7 +1,7 @@
'use client'
import type React from 'react'
-import { BulkActionButton, cn, Tooltip } from '@sim/emcn'
+import { BulkActionBar, BulkActionButton, cn, Tooltip } from '@sim/emcn'
import { Eye, PlayOutline, RefreshCw, Square } from '@sim/emcn/icons'
import { AnimatePresence, domAnimation, LazyMotion, m } from 'framer-motion'
@@ -89,47 +89,42 @@ export function TableActionBar({
className
)}
>
-
-
- {selectedCellCount === 1
+
+ : `Selected ${selectedCellCount} workflow cells`
+ }
+ >
+ {showPlay && (
+
+
+
+ )}
-
- {showPlay && (
-
-
-
- )}
+ {showRefresh && (
+
+
+
+ )}
- {showRefresh && (
-
-
-
- )}
+ {runningCount > 0 && (
+
+
+
+ )}
- {runningCount > 0 && (
-
-
-
- )}
-
- {onViewExecution && (
-
-
-
- )}
-
-
+ {onViewExecution && (
+
+
+
+ )}
+
)}
@@ -152,12 +147,7 @@ function ActionIconButton({ label, onClick, disabled, children }: ActionIconButt
return (
-
+
{children}
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx
index c005de9d781..53bd21ddf25 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx
@@ -1,7 +1,7 @@
'use client'
import { memo, useCallback, useMemo, useRef, useState } from 'react'
-import { Button, ChipDropdown, ChipInput, cn } from '@sim/emcn'
+import { Button, Chip, ChipInput, ChipSelect, cn } from '@sim/emcn'
import { Plus, X } from '@sim/emcn/icons'
import { generateShortId } from '@sim/utils/id'
import type { ColumnDefinition, FilterRule, TablePredicate } from '@/lib/table'
@@ -243,20 +243,19 @@ export function TableFilter({
))}
-
-
+
Add filter
-
+
{!autoApply && (
{filter !== null && (
-
+
Clear filters
-
+
)}
-
+
Apply filter
-
+
)}
@@ -329,37 +328,43 @@ const FilterRuleRow = memo(function FilterRuleRow({
)}
- onColumnChange(rule.id, value)}
placeholder='Column'
align='start'
- matchTriggerWidth={false}
- className='min-w-[100px]'
+ dropdownWidth='content'
+ className='w-auto min-w-[100px] max-w-none'
/>
- onUpdate(rule.id, 'operator', value)}
placeholder='Operator'
align='start'
- matchTriggerWidth={false}
- className='min-w-[90px]'
+ dropdownWidth='content'
+ className='w-auto min-w-[90px] max-w-none'
/>
{VALUELESS_OPS.has(rule.operator) ? (
) : isSelect ? (
- onUpdate(rule.id, 'value', value)}
placeholder='Select a value'
align='start'
- matchTriggerWidth={false}
- className='min-w-[100px] flex-1'
+ dropdownWidth='content'
+ className='w-auto min-w-[100px] max-w-none flex-1'
/>
) : autoApply ? (
Welcome message
-
- onChange(next as StatusFilter)}
options={STATUS_FILTER_OPTIONS}
- matchTriggerWidth={false}
- className='w-[140px] shrink-0'
+ dropdownWidth='content'
+ className='w-[140px] max-w-none shrink-0'
/>
)
}
@@ -184,22 +186,24 @@ function AllowlistField({ label, value, onChange, options, disabled }: Allowlist
{label}
-
)
diff --git a/apps/sim/ee/access-control/components/workspace-select.tsx b/apps/sim/ee/access-control/components/workspace-select.tsx
index 0b5c758c32a..ea1437e5d25 100644
--- a/apps/sim/ee/access-control/components/workspace-select.tsx
+++ b/apps/sim/ee/access-control/components/workspace-select.tsx
@@ -1,10 +1,10 @@
'use client'
-import { ChipDropdown, type ChipDropdownProps } from '@sim/emcn'
+import { ChipSelect, type ChipSelectProps, cn } from '@sim/emcn'
interface WorkspaceSelectProps
extends Pick<
- ChipDropdownProps,
+ ChipSelectProps,
'id' | 'aria-label' | 'aria-labelledby' | 'aria-describedby' | 'aria-required' | 'aria-invalid'
> {
workspaceIds: string[]
@@ -38,18 +38,26 @@ export function WorkspaceSelect({
...fieldAria
}: WorkspaceSelectProps) {
return (
-
)
}
diff --git a/apps/sim/ee/access-requests/components/organization-access-requests.tsx b/apps/sim/ee/access-requests/components/organization-access-requests.tsx
index e989f8cb369..6e45be61adb 100644
--- a/apps/sim/ee/access-requests/components/organization-access-requests.tsx
+++ b/apps/sim/ee/access-requests/components/organization-access-requests.tsx
@@ -1,6 +1,6 @@
'use client'
-import { Chip, ChipDropdown, ChipInput, ChipSwitch, ChipTag, toast } from '@sim/emcn'
+import { Chip, ChipInput, ChipSelect, ChipSwitch, ChipTag, toast } from '@sim/emcn'
import { Search } from '@sim/emcn/icons'
import { useQueryStates } from 'nuqs'
import { SEARCH_DEBOUNCE_MS } from '@/lib/url-state'
@@ -105,7 +105,11 @@ export function OrganizationAccessRequests({
void setParams({
diff --git a/apps/sim/ee/credential-groups/components/credential-group-add-resource-modal.test.tsx b/apps/sim/ee/credential-groups/components/credential-group-add-resource-modal.test.tsx
index 80b36c8841e..74cc9afcfbd 100644
--- a/apps/sim/ee/credential-groups/components/credential-group-add-resource-modal.test.tsx
+++ b/apps/sim/ee/credential-groups/components/credential-group-add-resource-modal.test.tsx
@@ -4,6 +4,7 @@
import type { ReactNode } from 'react'
import { act } from 'react'
+import type { ChipSelectProps } from '@sim/emcn'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
@@ -35,49 +36,33 @@ vi.mock('@sim/emcn', () => ({
),
- ChipDropdown: ({
- options,
- value,
- onChange,
- }: {
- options: Array<{ value: string; label: string }>
- value: string[]
- onChange: (value: string[]) => void
- }) => (
+ ChipSelect: (props: ChipSelectProps) => (
- {options.map((option) => (
+ {props.options?.map((option) => (
- onChange(
- value.includes(option.value)
- ? value.filter((id) => id !== option.value)
- : [...value, option.value]
- )
+ aria-pressed={
+ props.multiSelect
+ ? props.multiSelectValues?.includes(option.value)
+ : props.value === option.value
}
+ onClick={() => {
+ if (props.multiSelect) {
+ const values = props.multiSelectValues ?? []
+ props.onMultiSelectChange?.(
+ values.includes(option.value)
+ ? values.filter((id) => id !== option.value)
+ : [...values, option.value]
+ )
+ } else props.onChange?.(option.value)
+ }}
>
{option.label}
))}
),
- ChipSelect: ({
- options,
- onChange,
- }: {
- options: Array<{ value: string; label: string }>
- onChange: (value: string) => void
- }) => (
-