improvement(emcn): extract design system into shared @sim/emcn package (#5257)

Moves apps/sim/components/emcn into a shared @sim/emcn package consumed directly by apps/sim and apps/docs. cn/keyboard/use-copy-to-clipboard move into the package; all imports become direct @sim/emcn (icons via @sim/emcn/icons, CSS via file path). ChipModal email validation is now prop-driven (quickValidateEmail stays in apps/sim, injected via validate). Docs drops its local chip/chip-dropdown/dropdown-menu mirrors and consumes @sim/emcn.
This commit is contained in:
Waleed
2026-06-28 22:50:48 -07:00
committed by GitHub
parent d878f15815
commit bcf6a804f9
692 changed files with 1352 additions and 1774 deletions
+2 -2
View File
@@ -5,13 +5,13 @@ paths:
# EMCN Components
Import from `@/components/emcn`, never from subpaths (except CSS files). 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`/`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).
## 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`, and `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells). All are re-exported from the `@/components/emcn` barrel — no subpath import needed.
- **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`, and `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells). 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.
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.
+1 -1
View File
@@ -47,7 +47,7 @@ import { CORE_TRIGGER_TYPES } from '@/app/workspace/.../utils'
1. React/core libraries
2. External libraries
3. UI components (`@/components/emcn`, `@/components/ui`)
3. UI components (`@sim/emcn`, `@/components/ui`)
4. Utilities (`@/lib/...`)
5. Stores (`@/stores/...`)
6. Feature imports
+2 -2
View File
@@ -16,7 +16,7 @@ paths:
## Conditional Classes
```typescript
import { cn } from '@/lib/core/utils/cn'
import { cn } from '@sim/emcn'
<div className={cn(
'base-classes',
@@ -52,7 +52,7 @@ Value text `--text-body`; muted/placeholder/labels `--text-muted`; icons `--text
## Chip Components (consumer usage)
`ChipInput`, `ChipTextarea`, `ChipModal*` own their full chrome. Consumers describe intent through PROPS; they never re-style the chrome. The canonical chrome lives in `apps/sim/components/emcn/components/chip/chip-chrome.ts` (all tokens are re-exported from the `@/components/emcn` barrel — no subpath import needed) — never hand-roll `rounded-lg`/`border`/`bg-[var(--surface-5)]`/`h-[30px]`/`px-2`/`text-sm`/focus rings.
`ChipInput`, `ChipTextarea`, `ChipModal*` own their full chrome. Consumers describe intent through PROPS; they never re-style the chrome. The canonical chrome lives in `apps/sim/components/emcn/components/chip/chip-chrome.ts` (all tokens are re-exported from the `@sim/emcn` barrel — no subpath import needed) — never hand-roll `rounded-lg`/`border`/`bg-[var(--surface-5)]`/`h-[30px]`/`px-2`/`text-sm`/focus rings.
### Props over className
+2 -2
View File
@@ -4,13 +4,13 @@ globs: ["apps/sim/components/emcn/**"]
---
# EMCN Components
Import from `@/components/emcn`, never from subpaths (except CSS files). 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 from `@sim/emcn`, never from subpaths (except CSS files). 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).
## 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`, and `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells). All are re-exported from the `@/components/emcn` barrel — no subpath import needed.
- **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`, and `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells). 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.
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.
+1 -1
View File
@@ -46,7 +46,7 @@ import { CORE_TRIGGER_TYPES } from '@/app/workspace/.../utils'
1. React/core libraries
2. External libraries
3. UI components (`@/components/emcn`, `@/components/ui`)
3. UI components (`@sim/emcn`, `@/components/ui`)
4. Utilities (`@/lib/...`)
5. Stores (`@/stores/...`)
6. Feature imports
+2 -2
View File
@@ -14,7 +14,7 @@ globs: ["apps/sim/**/*.tsx", "apps/sim/**/*.css"]
## Conditional Classes
```typescript
import { cn } from '@/lib/core/utils/cn'
import { cn } from '@sim/emcn'
<div className={cn(
'base-classes',
@@ -50,7 +50,7 @@ Value text `--text-body`; muted/placeholder/labels `--text-muted`; icons `--text
## Chip Components (consumer usage)
`ChipInput`, `ChipTextarea`, `ChipModal*` own their full chrome. Consumers describe intent through PROPS; they never re-style the chrome. The canonical chrome lives in `apps/sim/components/emcn/components/chip/chip-chrome.ts` (all tokens are re-exported from the `@/components/emcn` barrel — no subpath import needed) — never hand-roll `rounded-lg`/`border`/`bg-[var(--surface-5)]`/`h-[30px]`/`px-2`/`text-sm`/focus rings.
`ChipInput`, `ChipTextarea`, `ChipModal*` own their full chrome. Consumers describe intent through PROPS; they never re-style the chrome. The canonical chrome lives in `apps/sim/components/emcn/components/chip/chip-chrome.ts` (all tokens are re-exported from the `@sim/emcn` barrel — no subpath import needed) — never hand-roll `rounded-lg`/`border`/`bg-[var(--surface-5)]`/`h-[30px]`/`px-2`/`text-sm`/focus rings.
### Props over className
+3 -3
View File
@@ -93,7 +93,7 @@ Use barrel exports (`index.ts`) when a folder has 3+ exports. Do not re-export f
1. React/core libraries
2. External libraries
3. UI components (`@/components/emcn`, `@/components/ui`)
3. UI components (`@sim/emcn`, `@/components/ui`)
4. Utilities (`@/lib/...`)
5. Stores (`@/stores/...`)
6. Feature imports
@@ -375,7 +375,7 @@ Co-locate a `search-params.ts` per feature exporting the parser map (single sour
## Styling
Use Tailwind only, no inline styles. Use `cn()` from `@/lib/core/utils/cn` for conditional classes.
Use Tailwind only, no inline styles. Use `cn()` from `@sim/emcn` for conditional classes.
```typescript
<div className={cn('base-classes', isActive && 'active-classes')} />
@@ -391,7 +391,7 @@ On chip components (see "EMCN Components"), drive chrome through PROPS, not `cla
## EMCN Components
Import from `@/components/emcn`, never from subpaths (except CSS files). Use CVA only when 2+ genuine variants exist; otherwise plain `cn()`.
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`.
+1 -1
View File
@@ -1,5 +1,5 @@
import { ChipLink } from '@sim/emcn'
import { DocsPage } from 'fumadocs-ui/page'
import { ChipLink } from '@/components/ui/chip'
export const metadata = {
title: 'Page Not Found',
+12
View File
@@ -59,8 +59,14 @@ body {
--text-icon: #525252;
--text-inverse: #ffffff;
--text-error: #ef4444;
--text-muted-inverse: #a0a0a0;
--brand-accent: #33c482;
--brand-accent-hover: #2dac72;
--brand-secondary: #33b4ff;
--surface-inverted: #1b1b1b;
--surface-inverted-hover: #363636;
--border-inverted: #363636;
--border-muted: #e4e4e4;
--badge-success-bg: #bbf7d0;
--badge-success-text: #15803d;
--badge-blue-bg: #bfdbfe;
@@ -102,8 +108,14 @@ body {
--text-icon: #a0a0a0;
--text-inverse: #1b1b1b;
--text-error: #ef4444;
--text-muted-inverse: #b3b3b3;
--brand-accent: #33c482;
--brand-accent-hover: #2dac72;
--brand-secondary: #33b4ff;
--surface-inverted: #242424;
--surface-inverted-hover: #363636;
--border-inverted: #3d3d3d;
--border-muted: #424242;
--badge-success-bg: rgba(34, 197, 94, 0.2);
--badge-success-text: #86efac;
--badge-blue-bg: rgba(59, 130, 246, 0.2);
+1 -1
View File
@@ -1,8 +1,8 @@
'use client'
import { ChipLink } from '@sim/emcn'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { ChipLink } from '@/components/ui/chip'
import { LanguageDropdown } from '@/components/ui/language-dropdown'
import { SearchTrigger } from '@/components/ui/search-trigger'
import { SimLogoFull } from '@/components/ui/sim-logo'
+1 -1
View File
@@ -1,8 +1,8 @@
'use client'
import { Chip } from '@sim/emcn'
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'
import { Check, Copy } from 'lucide-react'
import { Chip } from '@/components/ui/chip'
export function LLMCopyButton({ content }: { content: string }) {
const [checked, onClick] = useCopyButton(() => navigator.clipboard.writeText(content))
-140
View File
@@ -1,140 +0,0 @@
'use client'
import { type ComponentType, forwardRef, type ReactNode } from 'react'
import { Check } from 'lucide-react'
import { ChipChevronDown, chipVariants, TRIGGER_BORDER_CLASS } from '@/components/ui/chip'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { cn } from '@/lib/utils'
type ChipIcon = ComponentType<{ className?: string }>
/**
* Single option rendered inside a {@link ChipDropdown}.
*/
interface ChipDropdownOption {
/** Stable identifier returned via `onChange`. */
value: string
/** Visual label rendered both inside the trigger (when selected) and in the menu. */
label: ReactNode
/** Optional leading icon rendered inside the menu item. */
icon?: ChipIcon
/** Pre-rendered leading element (e.g. a flag) — takes precedence over `icon`. */
iconElement?: ReactNode
}
interface ChipDropdownProps {
/** Options to render in the menu. */
options: ReadonlyArray<ChipDropdownOption>
/** Currently selected value. */
value?: string
/** Called when the user picks a different option from the menu. */
onChange?: (value: string) => void
/** Shown in the trigger when nothing is selected. */
placeholder?: string
/** Aligns the dropdown popover relative to the trigger. */
align?: 'start' | 'center' | 'end'
/**
* Whether the menu width should match the trigger's. When `true` (default),
* the menu pins to `--radix-dropdown-menu-trigger-width`. Set `false` to let
* the menu size to its widest item.
*/
matchTriggerWidth?: boolean
/** Forwarded to the inner `DropdownMenuContent`. */
contentClassName?: string
/** Disables the trigger. */
disabled?: boolean
/** Optional icon rendered before the label. */
leftIcon?: ChipIcon
/** Forwarded class for the trigger button — layout/sizing only. */
className?: string
}
/**
* Docs-local mirror of the emcn `ChipDropdown`
* (`apps/sim/components/emcn/components/chip-dropdown/chip-dropdown.tsx`) — a
* 30px filled chip pill that opens a menu of options. The trigger reuses
* `chipVariants` for visual parity with `Chip`; the chevron is owned by the
* component and rendered in a 16px slot so the label's trailing gap matches a
* leading icon's.
*
* @example
* <ChipDropdown
* value={currentLang}
* onChange={setLanguage}
* options={LANGUAGE_OPTIONS}
* align='end'
* matchTriggerWidth={false}
* />
*/
const ChipDropdown = forwardRef<HTMLButtonElement, ChipDropdownProps>(function ChipDropdown(
{
options,
value,
onChange,
placeholder,
align = 'end',
matchTriggerWidth = true,
contentClassName,
disabled,
leftIcon: LeftIcon,
className,
},
ref
) {
const selected = options.find((option) => option.value === value)
const displayLabel = selected?.label ?? placeholder ?? 'Select...'
const isPlaceholder = selected == null
return (
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild disabled={disabled}>
<button
ref={ref}
type='button'
disabled={disabled}
className={cn(chipVariants({ variant: 'filled' }), TRIGGER_BORDER_CLASS, className)}
>
{LeftIcon ? (
<LeftIcon className='size-[16px] flex-shrink-0 text-[var(--text-icon)]' />
) : null}
<span
className={cn(
'min-w-0 flex-1 truncate text-sm',
isPlaceholder ? 'text-[var(--text-muted)]' : 'text-[var(--text-body)]'
)}
>
{displayLabel}
</span>
<ChipChevronDown />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align={align}
className={cn(
matchTriggerWidth && 'w-[var(--radix-dropdown-menu-trigger-width)]',
contentClassName
)}
>
{options.map((option) => {
const isSelected = option.value === value
const OptionIcon = option.icon
return (
<DropdownMenuItem key={option.value} onSelect={() => onChange?.(option.value)}>
{option.iconElement ?? (OptionIcon ? <OptionIcon /> : null)}
<span>{option.label}</span>
{isSelected ? <Check className='!ml-auto !size-[16px]' /> : null}
</DropdownMenuItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
)
})
export { ChipDropdown }
export type { ChipDropdownOption, ChipDropdownProps }
-175
View File
@@ -1,175 +0,0 @@
'use client'
import {
type AnchorHTMLAttributes,
type ButtonHTMLAttributes,
type ComponentType,
forwardRef,
type ReactNode,
} from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import Link, { type LinkProps } from 'next/link'
import { cn } from '@/lib/utils'
/**
* Docs-local mirror of the platform chip chrome
* (`apps/sim/components/emcn/components/chip/chip-chrome.ts`). The docs app
* cannot import from `apps/sim`, so the canonical tokens are replicated here —
* keep them in sync with the emcn source.
*/
export const chipContentGap = 'gap-1.5'
/** Chip pill geometry — height, centering, gap, radius, padding, text size. */
export const chipGeometryClass = `h-[30px] items-center ${chipContentGap} rounded-lg px-2 text-left text-sm`
/** Chip-content icon (non-inverse): 16px, non-shrinking, `--text-icon`. */
export const chipContentIconClass = 'size-[16px] flex-shrink-0 text-[var(--text-icon)]'
/** Chip-content label (non-inverse): truncating `--text-body` at `text-sm`. */
export const chipContentLabelClass = 'min-w-0 truncate text-[var(--text-body)] text-sm'
/** The filled FILL (surface only, no border) — `--surface-5` light / `--surface-4` dark. */
export const chipFilledFillTokens = 'bg-[var(--surface-5)] dark:bg-[var(--surface-4)]'
/** The inverse/primary FILL — dark surface + inverse text in light, white + `--bg` text in dark. */
const chipPrimaryFillTokens =
'bg-[var(--text-primary)] text-[var(--text-inverse)] dark:bg-white dark:text-[var(--bg)]'
/** 1px `--border-1` border applied to chip triggers so they read as controls. */
export const TRIGGER_BORDER_CLASS = 'border border-[var(--border-1)]'
/**
* 30px pill — the platform's most common chrome pattern, mirrored for the docs.
*
* @remarks
* The implicit default variant is the bare pill — transparent, `--surface-active`
* on hover. `filled` adds the filled surface; `primary` is the canonical inverse
* CTA surface (dark in light mode, white in dark mode) used for the "Get started"
* link.
*/
const chipVariants = cva(
`group cursor-pointer font-season ${chipGeometryClass} transition-colors disabled:cursor-not-allowed disabled:opacity-60`,
{
variants: {
variant: {
default: 'hover:bg-[var(--surface-active)]',
filled: `${chipFilledFillTokens} hover:bg-[var(--surface-active)]`,
primary: `${chipPrimaryFillTokens} hover:bg-[var(--text-body)] dark:hover:bg-[var(--text-secondary)]`,
},
fullWidth: { true: 'flex', false: 'inline-flex' },
},
defaultVariants: { variant: 'default', fullWidth: false },
}
)
type ChipIcon = ComponentType<{ className?: string }>
type ChipVariant = VariantProps<typeof chipVariants>['variant']
interface ChipBaseProps extends VariantProps<typeof chipVariants> {
/** Icon component rendered before the label. */
leftIcon?: ChipIcon
/** Icon component rendered after the label. */
rightIcon?: ChipIcon
children?: ReactNode
}
/**
* `primary` sets text color on the chip itself — its icon and label inherit via
* `currentColor`. The default and `filled` chips use explicit icon
* (`--text-icon`) and label (`--text-body`) colors.
*/
function ChipContent({
variant,
leftIcon: LeftIcon,
rightIcon: RightIcon,
children,
}: ChipBaseProps) {
const isInverse = variant === 'primary'
const iconClass = cn(chipContentIconClass, isInverse && 'text-current')
const labelClass = cn(chipContentLabelClass, 'flex-1', isInverse && 'text-current')
return (
<>
{LeftIcon ? <LeftIcon className={iconClass} /> : null}
{children != null && children !== false ? (
<span className={labelClass}>{children}</span>
) : null}
{RightIcon ? <RightIcon className={iconClass} /> : null}
</>
)
}
interface ChipProps
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'>,
ChipBaseProps {}
/**
* @example <Chip leftIcon={Copy} onClick={copyPage}>Copy page</Chip>
*/
const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
{ className, variant, fullWidth, leftIcon, rightIcon, children, type, ...props },
ref
) {
return (
<button
ref={ref}
type={type ?? 'button'}
className={cn(chipVariants({ variant, fullWidth }), className)}
{...props}
>
<ChipContent variant={variant} leftIcon={leftIcon} rightIcon={rightIcon}>
{children}
</ChipContent>
</button>
)
})
interface ChipLinkProps
extends Omit<LinkProps, 'children'>,
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps | 'children'>,
ChipBaseProps {}
/**
* @example <ChipLink href='https://sim.ai' variant='primary'>Get started</ChipLink>
*/
const ChipLink = forwardRef<HTMLAnchorElement, ChipLinkProps>(function ChipLink(
{ className, variant, fullWidth, leftIcon, rightIcon, children, ...props },
ref
) {
return (
<Link ref={ref} className={cn(chipVariants({ variant, fullWidth }), className)} {...props}>
<ChipContent variant={variant} leftIcon={leftIcon} rightIcon={rightIcon}>
{children}
</ChipContent>
</Link>
)
})
interface ChipChevronDownProps {
/** Layout-only extras (e.g. `ml-auto` to push the chevron flush right). Never chrome. */
className?: string
}
/**
* Canonical trailing chevron adornment for chip-style dropdown triggers — a
* 16px hidden-from-AT slot centering the 10×6 chevron glyph in `--text-icon`,
* matching the chevron `ChipDropdown` owns in the main app.
*/
export function ChipChevronDown({ className }: ChipChevronDownProps) {
return (
<span
aria-hidden
className={cn(
'inline-flex size-[16px] flex-shrink-0 items-center justify-center text-[var(--text-icon)]',
className
)}
>
<svg width='10' height='6' viewBox='0 0 10 6' fill='none'>
<path
d='M1 1L5 5L9 1'
stroke='currentColor'
strokeWidth='1.33'
strokeLinecap='square'
strokeLinejoin='miter'
/>
</svg>
</span>
)
}
export { Chip, ChipLink, chipVariants }
export type { ChipLinkProps, ChipProps, ChipVariant }
-61
View File
@@ -1,61 +0,0 @@
'use client'
import type { ComponentPropsWithRef } from 'react'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { cn } from '@/lib/utils'
const ANIMATION_CLASSES =
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=open]:animate-in motion-reduce:animate-none'
/**
* Menu surface mirrored from the emcn `DropdownMenu`
* (`apps/sim/components/emcn/components/dropdown-menu/dropdown-menu.tsx`) —
* `--bg` surface, `--border` outline, `rounded-xl`, `p-1.5`.
*/
const CONTENT_BASE_CLASSES =
'z-50 max-h-[240px] min-w-[8rem] origin-[var(--radix-dropdown-menu-content-transform-origin)] overflow-y-auto overflow-x-hidden overscroll-none border border-[var(--border)] bg-[var(--bg)] p-1.5 font-season text-[var(--text-body)] shadow-sm'
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
function DropdownMenuContent({
className,
sideOffset = 6,
ref,
...props
}: ComponentPropsWithRef<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
ANIMATION_CLASSES,
CONTENT_BASE_CLASSES,
'max-w-[220px] rounded-xl',
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
)
}
function DropdownMenuItem({
className,
ref,
...props
}: ComponentPropsWithRef<typeof DropdownMenuPrimitive.Item>) {
return (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex h-[30px] min-w-0 cursor-pointer select-none items-center gap-2 rounded-lg px-2 text-[13px] text-[var(--text-body)] outline-none transition-colors focus:bg-[var(--surface-active)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>span]:min-w-0 [&>span]:truncate [&_svg]:pointer-events-none [&_svg]:size-[14px] [&_svg]:shrink-0 [&_svg]:text-[var(--text-icon)]',
className
)}
{...props}
/>
)
}
export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem }
@@ -1,7 +1,7 @@
'use client'
import { ChipDropdown } from '@sim/emcn'
import { useParams, usePathname, useRouter } from 'next/navigation'
import { ChipDropdown } from '@/components/ui/chip-dropdown'
const languages = {
en: { name: 'English', flag: '🇺🇸' },
+2 -2
View File
@@ -1,12 +1,12 @@
'use client'
import { Search } from 'lucide-react'
import {
chipContentIconClass,
chipFilledFillTokens,
chipGeometryClass,
TRIGGER_BORDER_CLASS,
} from '@/components/ui/chip'
} from '@sim/emcn'
import { Search } from 'lucide-react'
import { cn } from '@/lib/utils'
export function SearchTrigger() {
+1
View File
@@ -5,6 +5,7 @@ const withMDX = createMDX()
const config: NextConfig = {
reactStrictMode: true,
transpilePackages: ['@sim/emcn'],
images: {
unoptimized: true,
},
+1
View File
@@ -18,6 +18,7 @@
"@ai-sdk/openai": "2.0.107",
"@ai-sdk/react": "2.0.205",
"@sim/db": "workspace:*",
"@sim/emcn": "workspace:*",
"@vercel/og": "^0.6.5",
"ai": "5.0.203",
"class-variance-authority": "^0.7.1",
@@ -1,4 +1,4 @@
import { cn } from '@/lib/core/utils/cn'
import { cn } from '@sim/emcn'
import AuthBackgroundSVG from '@/app/(auth)/components/auth-background-svg'
type AuthBackgroundProps = {
@@ -1,7 +1,7 @@
'use client'
import { type ReactNode, useState } from 'react'
import { Button } from '@/components/emcn'
import { Button } from '@sim/emcn'
import { GithubIcon, GoogleIcon, MicrosoftIcon } from '@/components/icons'
import { client } from '@/lib/auth/auth-client'
@@ -1,9 +1,7 @@
'use client'
import { Button, cn } from '@sim/emcn'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/emcn'
import { getEnv, isTruthy } from '@/lib/core/config/env'
import { cn } from '@/lib/core/utils/cn'
import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes'
interface SSOLoginButtonProps {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function LoginLoading() {
return (
+7 -7
View File
@@ -1,11 +1,6 @@
'use client'
import { useEffect, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { Eye, EyeOff } from 'lucide-react'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import {
ChipModal,
ChipModalBody,
@@ -13,16 +8,21 @@ import {
ChipModalField,
ChipModalFooter,
ChipModalHeader,
cn,
Input,
Label,
Loader,
} from '@/components/emcn'
} from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { Eye, EyeOff } from 'lucide-react'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { requestJson } from '@/lib/api/client/request'
import { forgetPasswordContract } from '@/lib/api/contracts'
import { client } from '@/lib/auth/auth-client'
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
import { cn } from '@/lib/core/utils/cn'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import { captureClientEvent } from '@/lib/posthog/client'
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function ResetPasswordLoading() {
return (
@@ -1,9 +1,8 @@
'use client'
import { useState } from 'react'
import { cn, Input, Label, Loader } from '@sim/emcn'
import { Eye, EyeOff } from 'lucide-react'
import { Input, Label, Loader } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes'
interface RequestResetFormProps {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function SignupLoading() {
return (
+1 -2
View File
@@ -2,16 +2,15 @@
import { Suspense, useEffect, useMemo, useRef, useState } from 'react'
import { Turnstile, type TurnstileInstance } from '@marsidev/react-turnstile'
import { cn, Input, Label, Loader } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { Eye, EyeOff } from 'lucide-react'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { usePostHog } from 'posthog-js/react'
import { Input, Label, Loader } from '@/components/emcn'
import { client, useSession } from '@/lib/auth/auth-client'
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
import { cn } from '@/lib/core/utils/cn'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import { captureClientEvent, captureEvent } from '@/lib/posthog/client'
import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes'
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function SSOLoading() {
return (
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function VerifyLoading() {
return (
@@ -1,9 +1,8 @@
'use client'
import { Suspense, useEffect, useState } from 'react'
import { cn, InputOTP, InputOTPGroup, InputOTPSlot, Loader } from '@sim/emcn'
import { useRouter } from 'next/navigation'
import { InputOTP, InputOTPGroup, InputOTPSlot, Loader } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes'
import { useVerification } from '@/app/(auth)/verify/use-verification'
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function BlogPostLoading() {
return (
+1 -1
View File
@@ -1,7 +1,7 @@
import { Avatar, AvatarFallback, AvatarImage } from '@sim/emcn'
import type { Metadata } from 'next'
import Image from 'next/image'
import Link from 'next/link'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/emcn'
import { FAQ } from '@/lib/blog/faq'
import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/blog/registry'
import { buildPostGraphJsonLd, buildPostMetadata } from '@/lib/blog/seo'
@@ -1,14 +1,9 @@
'use client'
import { useState } from 'react'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@sim/emcn'
import { Duplicate } from '@sim/emcn/icons'
import { Share2 } from 'lucide-react'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/emcn'
import { Duplicate } from '@/components/emcn/icons'
import { LinkedInIcon, xIcon as XIcon } from '@/components/icons'
interface ShareButtonProps {
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
const SKELETON_POST_COUNT = 4
@@ -1,8 +1,8 @@
'use client'
import { useState } from 'react'
import { cn } from '@sim/emcn'
import NextImage from 'next/image'
import { cn } from '@/lib/core/utils/cn'
import { Lightbox } from '@/app/(landing)/blog/components/lightbox'
interface BlogImageProps {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function BlogLoading() {
return (
+1 -1
View File
@@ -1,7 +1,7 @@
import { Badge } from '@sim/emcn'
import type { Metadata } from 'next'
import Image from 'next/image'
import Link from 'next/link'
import { Badge } from '@/components/emcn'
import { getAllPostMeta } from '@/lib/blog/registry'
import { buildCollectionPageJsonLd } from '@/lib/blog/seo'
import { SITE_URL } from '@/lib/core/utils/urls'
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
const SKELETON_TAG_COUNT = 12
@@ -1,10 +1,6 @@
'use client'
import { useEffect, useMemo, useState } from 'react'
import { createLogger } from '@sim/logger'
import { X } from 'lucide-react'
import Image from 'next/image'
import { useRouter } from 'next/navigation'
import {
Loader,
Modal,
@@ -13,7 +9,11 @@ import {
ModalDescription,
ModalTitle,
ModalTrigger,
} from '@/components/emcn'
} from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { X } from 'lucide-react'
import Image from 'next/image'
import { useRouter } from 'next/navigation'
import { GithubIcon, GoogleIcon, MicrosoftIcon } from '@/components/icons'
import { requestJson } from '@/lib/api/client/request'
import { type AuthProviderStatusResponse, getAuthProvidersContract } from '@/lib/api/contracts/auth'
@@ -1,10 +1,10 @@
'use client'
import { useCallback, useId, useRef, useState } from 'react'
import { Badge } from '@sim/emcn'
import dynamic from 'next/dynamic'
import Image from 'next/image'
import Link from 'next/link'
import { Badge } from '@/components/emcn'
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
const AuthModal = dynamic(
@@ -2,11 +2,11 @@
import { useRef, useState } from 'react'
import { Turnstile, type TurnstileInstance } from '@marsidev/react-turnstile'
import { Combobox, Input, Textarea } from '@sim/emcn'
import { Check } from '@sim/emcn/icons'
import { toError } from '@sim/utils/errors'
import { useMutation } from '@tanstack/react-query'
import Link from 'next/link'
import { Combobox, Input, Textarea } from '@/components/emcn'
import { Check } from '@/components/emcn/icons'
import { requestJson } from '@/lib/api/client/request'
import {
CONTACT_TOPIC_OPTIONS,
@@ -1,8 +1,6 @@
'use client'
import { useState } from 'react'
import { getErrorMessage } from '@sim/utils/errors'
import { useMutation } from '@tanstack/react-query'
import {
ChipCombobox,
ChipInput,
@@ -14,8 +12,10 @@ import {
ModalFooter,
ModalHeader,
ModalTrigger,
} from '@/components/emcn'
import { Check } from '@/components/emcn/icons'
} from '@sim/emcn'
import { Check } from '@sim/emcn/icons'
import { getErrorMessage } from '@sim/utils/errors'
import { useMutation } from '@tanstack/react-query'
import { requestJson } from '@/lib/api/client/request'
import {
DEMO_REQUEST_COMPANY_SIZE_OPTIONS,
@@ -4,8 +4,8 @@ import { type SVGProps, useEffect, useRef, useState } from 'react'
import { AnimatePresence, domAnimation, LazyMotion, m, useInView } from 'framer-motion'
import { Streamdown } from 'streamdown'
import 'streamdown/styles.css'
import { ChevronDown } from '@/components/emcn'
import { Database, File, Library, Table, Workflow } from '@/components/emcn/icons'
import { ChevronDown, cn } from '@sim/emcn'
import { Database, File, Library, Table, Workflow } from '@sim/emcn/icons'
import {
AnthropicIcon,
GeminiIcon,
@@ -17,7 +17,6 @@ import {
SlackIcon,
xAIIcon,
} from '@/components/icons'
import { cn } from '@/lib/core/utils/cn'
interface FeaturesPreviewProps {
activeTab: number
@@ -1,6 +1,7 @@
'use client'
import { useRef, useState } from 'react'
import { Badge } from '@sim/emcn'
import {
domAnimation,
LazyMotion,
@@ -11,7 +12,6 @@ import {
} from 'framer-motion'
import dynamic from 'next/dynamic'
import Image from 'next/image'
import { Badge } from '@/components/emcn'
import { FeaturesPreview } from '@/app/(landing)/components/features/components/features-preview'
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
@@ -1,10 +1,9 @@
'use client'
import { useCallback, useRef, useState } from 'react'
import { cn, handleKeyboardActivation } from '@sim/emcn'
import { ArrowUp } from 'lucide-react'
import dynamic from 'next/dynamic'
import { cn } from '@/lib/core/utils/cn'
import { handleKeyboardActivation } from '@/lib/core/utils/keyboard'
import { captureClientEvent } from '@/lib/posthog/client'
import { useLandingSubmit } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
@@ -1,6 +1,6 @@
import { cn } from '@sim/emcn'
import Image from 'next/image'
import Link from 'next/link'
import { cn } from '@/lib/core/utils/cn'
import { FooterCTA } from '@/app/(landing)/components/footer/footer-cta'
const LINK_CLASS =
@@ -1,7 +1,7 @@
'use client'
import { cn } from '@sim/emcn'
import dynamic from 'next/dynamic'
import { cn } from '@/lib/core/utils/cn'
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
const AuthModal = dynamic(
@@ -1,9 +1,8 @@
'use client'
import { useId, useState } from 'react'
import { ChevronDown, cn } from '@sim/emcn'
import { domAnimation, LazyMotion, m } from 'framer-motion'
import { ChevronDown } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
interface LandingFAQItem {
question: string
@@ -1,4 +1,4 @@
import { File } from '@/components/emcn/icons'
import { File } from '@sim/emcn/icons'
import { DocxIcon, PdfIcon } from '@/components/icons/document-icons'
import type {
PreviewColumn,
@@ -1,10 +1,10 @@
'use client'
import { memo, useCallback, useEffect, useRef, useState } from 'react'
import { Blimp, Checkbox, ChevronDown } from '@sim/emcn'
import { TypeBoolean, TypeNumber, TypeText } from '@sim/emcn/icons'
import { AnimatePresence, domAnimation, LazyMotion, m } from 'framer-motion'
import { ArrowUp, Table } from 'lucide-react'
import { Blimp, Checkbox, ChevronDown } from '@/components/emcn'
import { TypeBoolean, TypeNumber, TypeText } from '@/components/emcn/icons'
import { captureClientEvent } from '@/lib/posthog/client'
import { useLandingSubmit } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
import { EASE_OUT } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'
@@ -1,4 +1,4 @@
import { Database } from '@/components/emcn/icons'
import { Database } from '@sim/emcn/icons'
import {
AirtableIcon,
AsanaIcon,
@@ -1,10 +1,9 @@
'use client'
import { useMemo, useState } from 'react'
import { ArrowUpDown, Badge, Library, ListFilter, Search } from '@/components/emcn'
import type { BadgeProps } from '@/components/emcn/components/badge/badge'
import { Download, Workflow } from '@/components/emcn/icons'
import { cn } from '@/lib/core/utils/cn'
import type { BadgeProps } from '@sim/emcn'
import { ArrowUpDown, Badge, cn, Library, ListFilter, Search } from '@sim/emcn'
import { Download, Workflow } from '@sim/emcn/icons'
interface LogRow {
id: string
@@ -1,12 +1,12 @@
'use client'
import { memo, useCallback, useEffect, useRef, useState } from 'react'
import { Blimp, BubbleChatPreview, ChevronDown, MoreHorizontal, Play } from '@sim/emcn'
import { AnimatePresence, domMax, LazyMotion, m } from 'framer-motion'
import { ArrowUp } from 'lucide-react'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/navigation'
import { createPortal } from 'react-dom'
import { Blimp, BubbleChatPreview, ChevronDown, MoreHorizontal, Play } from '@/components/emcn'
import { AgentIcon, HubspotIcon, OpenAIIcon, SalesforceIcon } from '@/components/icons'
import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
import { captureClientEvent } from '@/lib/posthog/client'
@@ -2,8 +2,7 @@
import type { ReactNode } from 'react'
import { useMemo, useState } from 'react'
import { ArrowUpDown, ListFilter, Plus, Search } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { ArrowUpDown, cn, ListFilter, Plus, Search } from '@sim/emcn'
export interface PreviewColumn {
id: string
@@ -1,4 +1,4 @@
import { Calendar } from '@/components/emcn/icons'
import { Calendar } from '@sim/emcn/icons'
import type {
PreviewColumn,
PreviewRow,
@@ -1,6 +1,5 @@
'use client'
import { ChevronDown, Home, Library } from '@/components/emcn'
import { ChevronDown, cn, Home, Library } from '@sim/emcn'
import {
Calendar,
Database,
@@ -10,8 +9,7 @@ import {
Settings,
Table,
Workflow,
} from '@/components/emcn/icons'
import { cn } from '@/lib/core/utils/cn'
} from '@sim/emcn/icons'
import type { PreviewWorkflow } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'
export type SidebarView =
@@ -1,8 +1,7 @@
'use client'
import { useEffect, useState } from 'react'
import { AnimatePresence, domAnimation, LazyMotion, m } from 'framer-motion'
import { Checkbox } from '@/components/emcn'
import { Checkbox, cn } from '@sim/emcn'
import {
ChevronDown,
Columns3,
@@ -11,8 +10,8 @@ import {
TypeBoolean,
TypeNumber,
TypeText,
} from '@/components/emcn/icons'
import { cn } from '@/lib/core/utils/cn'
} from '@sim/emcn/icons'
import { AnimatePresence, domAnimation, LazyMotion, m } from 'framer-motion'
import type {
PreviewColumn,
PreviewRow,
@@ -1,10 +1,10 @@
'use client'
import { memo } from 'react'
import { Blimp } from '@sim/emcn'
import { domAnimation, LazyMotion, m } from 'framer-motion'
import { Database } from 'lucide-react'
import { Handle, type NodeProps, Position } from 'reactflow'
import { Blimp } from '@/components/emcn'
import {
AgentIcon,
AnthropicIcon,
@@ -1,6 +1,6 @@
import { cn } from '@sim/emcn'
import Image from 'next/image'
import Link from 'next/link'
import { cn } from '@/lib/core/utils/cn'
export interface NavBlogPost {
slug: string
@@ -1,11 +1,11 @@
'use client'
import { useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore } from 'react'
import { cn } from '@sim/emcn'
import dynamic from 'next/dynamic'
import Image from 'next/image'
import Link from 'next/link'
import { GithubOutlineIcon } from '@/components/icons'
import { cn } from '@/lib/core/utils/cn'
import { SessionContext } from '@/app/_shell/providers/session-provider'
import {
BlogDropdown,
@@ -1,7 +1,7 @@
'use client'
import { Badge } from '@sim/emcn'
import dynamic from 'next/dynamic'
import { Badge } from '@/components/emcn'
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
const AuthModal = dynamic(
@@ -1,13 +1,12 @@
'use client'
import { useCallback, useEffect, useState } from 'react'
import { Badge, ChevronDown, cn } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { AnimatePresence, domAnimation, LazyMotion, m } from 'framer-motion'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/navigation'
import { Badge, ChevronDown } from '@/components/emcn'
import { LandingWorkflowSeedStorage } from '@/lib/core/utils/browser-storage'
import { cn } from '@/lib/core/utils/cn'
import { TEMPLATE_WORKFLOWS } from '@/app/(landing)/components/templates/template-workflows'
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
@@ -1,8 +1,8 @@
'use client'
import { cn } from '@sim/emcn'
import { useRouter } from 'next/navigation'
import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
import { cn } from '@/lib/core/utils/cn'
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
interface TemplateCardButtonProps {
@@ -1,4 +1,4 @@
import { Loader } from '@/components/emcn'
import { Loader } from '@sim/emcn'
export default function IntegrationDetailLoading() {
return (
@@ -1,5 +1,5 @@
import { Badge } from '@sim/emcn'
import type { Metadata } from 'next'
import { Badge } from '@/components/emcn'
import { SITE_URL } from '@/lib/core/utils/urls'
import {
blockTypeToIconMap,
@@ -1,4 +1,4 @@
import { Loader } from '@/components/emcn'
import { Loader } from '@sim/emcn'
export default function IntegrationDetailLoading() {
return (
@@ -1,7 +1,7 @@
'use client'
import { useState } from 'react'
import { ChipInput, Search } from '@/components/emcn'
import { ChipInput, Search } from '@sim/emcn'
import { blockTypeToIconMap, formatIntegrationType, type Integration } from '@/lib/integrations'
import { IntegrationRow } from '@/app/(landing)/integrations/components/integration-card'
@@ -1,5 +1,5 @@
import type { ComponentType, ElementType, HTMLAttributes, SVGProps } from 'react'
import { cn } from '@/lib/core/utils/cn'
import { cn } from '@sim/emcn'
interface IntegrationIconProps extends HTMLAttributes<HTMLElement> {
bgColor: string
@@ -8,7 +8,7 @@ import {
ChipModalField,
ChipModalFooter,
ChipModalHeader,
} from '@/components/emcn'
} from '@sim/emcn'
import { requestJson } from '@/lib/api/client/request'
import { integrationRequestContract } from '@/lib/api/contracts/common'
@@ -1,4 +1,4 @@
import { Loader } from '@/components/emcn'
import { Loader } from '@sim/emcn'
export default function ModelDetailLoading() {
return (
@@ -1,4 +1,4 @@
import { Loader } from '@/components/emcn'
import { Loader } from '@sim/emcn'
export default function ModelProviderLoading() {
return (
@@ -1,7 +1,7 @@
import { Badge } from '@sim/emcn'
import type { Metadata } from 'next'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import { Badge } from '@/components/emcn'
import { SITE_URL } from '@/lib/core/utils/urls'
import { LandingFAQ } from '@/app/(landing)/components/landing-faq'
import {
@@ -1,5 +1,5 @@
import { Badge } from '@sim/emcn'
import type { Metadata } from 'next'
import { Badge } from '@/components/emcn'
import { SITE_URL } from '@/lib/core/utils/urls'
import { LandingFAQ } from '@/app/(landing)/components/landing-faq'
import { ModelComparisonCharts } from '@/app/(landing)/models/components/model-comparison-charts'
@@ -1,4 +1,4 @@
import { Loader } from '@/components/emcn'
import { Loader } from '@sim/emcn'
export default function ModelDetailLoading() {
return (
@@ -1,4 +1,4 @@
import { Loader } from '@/components/emcn'
import { Loader } from '@sim/emcn'
export default function ModelProviderLoading() {
return (
@@ -1,8 +1,8 @@
'use client'
import { useMemo, useState } from 'react'
import { Input } from '@sim/emcn'
import Link from 'next/link'
import { Input } from '@/components/emcn'
import { ChevronArrow, ProviderIcon } from '@/app/(landing)/models/components/model-primitives'
import {
type CatalogModel,
@@ -1,5 +1,5 @@
import { Badge } from '@sim/emcn'
import Link from 'next/link'
import { Badge } from '@/components/emcn'
import {
type CatalogModel,
type CatalogProvider,
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function PrivacyLoading() {
return (
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function TermsLoading() {
return (
@@ -1,6 +1,6 @@
'use client'
import { Tooltip } from '@/components/emcn'
import { Tooltip } from '@sim/emcn'
interface TooltipProviderProps {
children: React.ReactNode
@@ -1,9 +1,9 @@
'use client'
import { useMemo, useSyncExternalStore } from 'react'
import { Loader } from '@sim/emcn'
import { CheckCircle2, Circle, ExternalLink, GraduationCap } from 'lucide-react'
import Link from 'next/link'
import { Loader } from '@/components/emcn'
import {
getCompletedLessonsFromSnapshot,
getCompletedLessonsSnapshot,
@@ -1,10 +1,10 @@
'use client'
import { useState } from 'react'
import { cn } from '@sim/emcn'
import { CheckCircle2, XCircle } from 'lucide-react'
import { markLessonComplete } from '@/lib/academy/local-progress'
import type { QuizDefinition, QuizQuestion } from '@/lib/academy/types'
import { cn } from '@/lib/core/utils/cn'
interface LessonQuizProps {
lessonId: string
@@ -1,6 +1,7 @@
'use client'
import { useCallback, useEffect, useRef, useState } from 'react'
import { cn } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { sleep } from '@sim/utils/helpers'
import type { Edge } from 'reactflow'
@@ -12,7 +13,6 @@ import type {
ValidationResult,
} from '@/lib/academy/types'
import { validateExercise } from '@/lib/academy/validation'
import { cn } from '@/lib/core/utils/cn'
import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
@@ -1,8 +1,8 @@
'use client'
import { cn } from '@sim/emcn'
import { CheckCircle2, Circle } from 'lucide-react'
import type { ValidationRuleResult } from '@/lib/academy/types'
import { cn } from '@/lib/core/utils/cn'
interface ValidationChecklistProps {
results: ValidationRuleResult[]
@@ -3,7 +3,7 @@
import React from 'react'
import { Streamdown } from 'streamdown'
import 'streamdown/styles.css'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/emcn'
import { Avatar, AvatarFallback, AvatarImage } from '@sim/emcn'
import type { ChangelogEntry } from '@/app/changelog/components/changelog-content'
type Props = { initialEntries: ChangelogEntry[] }
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function ChangelogLoading() {
return (
+1 -1
View File
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function ChatLoading() {
return (
@@ -1,10 +1,9 @@
'use client'
import { useEffect, useState } from 'react'
import { cn, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, Loader } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { toError } from '@sim/utils/errors'
import { Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, Loader } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import AuthBackground from '@/app/(auth)/components/auth-background'
import { AUTH_SUBMIT_BTN, AUTH_TEXT_LINK } from '@/app/(auth)/components/auth-button-classes'
@@ -1,11 +1,10 @@
'use client'
import { useState } from 'react'
import { cn, Input, Label, Loader } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { toError } from '@sim/utils/errors'
import { Eye, EyeOff } from 'lucide-react'
import { Input, Label, Loader } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import AuthBackground from '@/app/(auth)/components/auth-background'
import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes'
import { SupportFooter } from '@/app/(auth)/components/support-footer'
+1 -3
View File
@@ -2,12 +2,10 @@
import type React from 'react'
import { useCallback, useLayoutEffect, useRef, useState } from 'react'
import { Badge, cn, handleKeyboardActivation, Tooltip } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { generateId } from '@sim/utils/id'
import { ArrowUp, Mic, Paperclip, X } from 'lucide-react'
import { Badge, Tooltip } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { handleKeyboardActivation } from '@/lib/core/utils/keyboard'
import { CHAT_ACCEPT_ATTRIBUTE } from '@/lib/uploads/utils/validation'
import { VoiceInput } from '@/app/chat/components/input/voice-input'
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export function ChatLoadingState() {
return (
@@ -3,7 +3,7 @@
*/
import { describe, expect, it, vi } from 'vitest'
vi.mock('@/components/emcn', () => ({
vi.mock('@sim/emcn', () => ({
Button: () => null,
Download: () => null,
Loader: () => null,
@@ -1,10 +1,10 @@
'use client'
import { useState } from 'react'
import { Button, Download, Loader } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { sleep } from '@sim/utils/helpers'
import { Music } from 'lucide-react'
import { Button, Download, Loader } from '@/components/emcn'
import { DefaultFileIcon, getDocumentIcon } from '@/components/icons/document-icons'
import { getBrowserOrigin } from '@/lib/core/utils/urls'
import type { ChatFile } from '@/app/chat/components/message/message'
@@ -1,7 +1,7 @@
import React, { type HTMLAttributes, memo, type ReactNode } from 'react'
import { Streamdown } from 'streamdown'
import 'streamdown/styles.css'
import { CopyCodeButton, Tooltip } from '@/components/emcn'
import { CopyCodeButton, Tooltip } from '@sim/emcn'
import { extractTextContent } from '@/lib/core/utils/react-node-text'
function LinkWithPreview({ href, children }: { href: string; children: React.ReactNode }) {
@@ -3,7 +3,7 @@
*/
import { describe, expect, it, vi } from 'vitest'
vi.mock('@/components/emcn', () => ({
vi.mock('@sim/emcn', () => ({
Duplicate: () => null,
Tooltip: {},
}))
@@ -1,8 +1,8 @@
'use client'
import { memo, useState } from 'react'
import { Duplicate, Tooltip } from '@sim/emcn'
import { Check, File as FileIcon, FileText, Image as ImageIcon } from 'lucide-react'
import { Duplicate, Tooltip } from '@/components/emcn'
import {
ChatFileDownload,
ChatFileDownloadAll,
@@ -1,13 +1,13 @@
'use client'
import { type RefObject, useCallback, useEffect, useRef, useState } from 'react'
import { cn } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import { Mic, MicOff, Phone } from 'lucide-react'
import dynamic from 'next/dynamic'
import { Button } from '@/components/ui/button'
import { requestJson } from '@/lib/api/client/request'
import { speechTokenContract } from '@/lib/api/contracts/media/speech'
import { cn } from '@/lib/core/utils/cn'
import { arrayBufferToBase64, floatTo16BitPCM } from '@/lib/speech/audio'
import {
CHUNK_SEND_INTERVAL_MS,
@@ -1,4 +1,4 @@
import { Skeleton } from '@/components/emcn'
import { Skeleton } from '@sim/emcn'
export default function CredentialAccountLoading() {
return (
+1 -2
View File
@@ -1,11 +1,10 @@
'use client'
import { useState } from 'react'
import { cn, Input, Label, Loader } from '@sim/emcn'
import { getErrorMessage } from '@sim/utils/errors'
import { Eye, EyeOff } from 'lucide-react'
import { useRouter } from 'next/navigation'
import { Input, Label, Loader } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes'
import { PublicFileAuthShell } from '@/app/f/[token]/public-file-auth-shell'
import { usePublicFileAuth } from '@/hooks/queries/public-shares'
@@ -1,10 +1,9 @@
'use client'
import { useEffect, useState } from 'react'
import { cn, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, Loader } from '@sim/emcn'
import { getErrorMessage } from '@sim/utils/errors'
import { useRouter } from 'next/navigation'
import { Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, Loader } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import { AUTH_SUBMIT_BTN, AUTH_TEXT_LINK } from '@/app/(auth)/components/auth-button-classes'
import { PublicFileAuthShell } from '@/app/f/[token]/public-file-auth-shell'

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