Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion apps/sim/app/(landing)/components/auth-modal/auth-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useEffect, useRef, useState } from 'react'
import {
cn,
Loader,
Modal,
ModalClose,
Expand All @@ -22,6 +23,7 @@ import { getEnv, isFalsy } from '@/lib/core/config/env'
import { isSsoEnabled } from '@/lib/core/config/env-flags'
import { captureClientEvent } from '@/lib/posthog/client'
import type { PostHogEventMap } from '@/lib/posthog/events'
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'
import { getBrandConfig } from '@/ee/whitelabeling'

const logger = createLogger('AuthModal')
Expand Down Expand Up @@ -196,7 +198,12 @@ export function AuthModal({ children, defaultView = 'login', source }: AuthModal
className='h-[22px] w-auto shrink-0 object-contain'
/>
<div className='flex flex-col gap-1 text-left'>
<p className='text-[22px] text-[color-mix(in_srgb,var(--text-muted)_60%,transparent)] leading-[125%] tracking-[0.02em]'>
<p
className={cn(
'text-[22px] leading-[125%] tracking-[0.02em]',
colorMixFallbacks.mutedText60
)}
>
Start building.
</p>
<h2 className='text-[22px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
TYPE_MS_PER_ATOM,
WORKFLOW_FOCUS_SCALE,
} from '@/app/(landing)/components/hero/components/hero-visual/workflow-data'
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'

/**
* Animated hero visual - the only client island in the hero, decorative and
Expand Down Expand Up @@ -299,7 +300,7 @@ const SEND_BUTTON_INK = {
*/
const LANDING_LOADER_INK = {
'--tl-grad-inner': 'var(--text-body)',
'--tl-grad-outer': 'color-mix(in srgb, var(--text-body) 76%, #fff)',
'--tl-grad-outer': 'var(--thinking-loader-outer)',
'--tl-glow': 'transparent',
} as CSSProperties

Expand Down Expand Up @@ -1144,6 +1145,7 @@ export function HeroVisual() {
// layer, so the slide + dock read as smooth sub-pixel motion instead of
// jittering as the position pixel-snaps each frame.
'pointer-events-none absolute top-0 left-0 z-20 transform-gpu transition-opacity duration-300 will-change-transform [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',
colorMixFallbacks.loaderOuter,
loaderFading ? 'opacity-0' : 'opacity-100'
)}
style={{ transformOrigin: '0 0' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { type CSSProperties, type ReactNode, useState } from 'react'
import { cn } from '@sim/emcn'
import { ThinkingLoader } from '@/components/ui'
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'

interface LogoMarkProps {
/** Server-rendered Sim wordmark, shown by default. */
Expand All @@ -18,7 +19,7 @@ interface LogoMarkProps {
*/
const LOADER_INK = {
'--tl-grad-inner': 'var(--text-body)',
'--tl-grad-outer': 'color-mix(in srgb, var(--text-body) 76%, #fff)',
'--tl-grad-outer': 'var(--thinking-loader-outer)',
'--tl-glow': 'transparent',
} as CSSProperties

Expand All @@ -35,7 +36,7 @@ export function LogoMark({ children }: LogoMarkProps) {

return (
<span
className='relative inline-flex items-center'
className={cn('relative inline-flex items-center', colorMixFallbacks.loaderOuter)}
// Transform + its transition are inline on purpose: Tailwind's
// `transition-transform` utility (its var-based transform composition)
// prevents the scale from applying on this element, so the transition is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
NAVBAR_GLASS_SURFACE,
useNavbarFrost,
} from '@/app/(landing)/components/navbar/components/navbar-shell'
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'

/**
Expand Down Expand Up @@ -92,7 +93,10 @@ export function MobileNav({ stars }: MobileNavProps) {
aria-hidden='true'
tabIndex={-1}
onClick={() => setOpen(false)}
className='fixed inset-0 top-full z-40 cursor-default bg-[color-mix(in_srgb,var(--text-primary)_8%,transparent)]'
className={cn(
'fixed inset-0 top-full z-40 cursor-default',
colorMixFallbacks.mobileBackdrop
)}
/>
) : null}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const ROW_CLASS =
const TITLE_CLASS = 'truncate text-[14px] text-[var(--text-body)]'
const DESC_CLASS = 'text-[12px] text-[var(--text-muted)] leading-snug'
const ARROW_CLASS =
'size-4 flex-shrink-0 -translate-x-1 text-[var(--text-icon)] opacity-0 transition-all group-hover/item:translate-x-0 group-hover/item:opacity-100 group-focus-visible/item:translate-x-0 group-focus-visible/item:opacity-100 motion-reduce:transition-none'
'size-4 flex-shrink-0 -translate-x-1 text-[var(--text-icon)] opacity-0 transition-[opacity,transform] group-hover/item:translate-x-0 group-hover/item:opacity-100 group-focus-visible/item:translate-x-0 group-focus-visible/item:opacity-100 motion-reduce:transition-none'

export function NavMenuItem({ item, onSelect }: NavMenuItemProps) {
const { title, description, href, external } = item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
import type { ReactNode } from 'react'
import { createContext, use, useEffect, useMemo, useRef, useState } from 'react'
import { cn } from '@sim/emcn'
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'

/**
* Frosted near-white surface for the scrolled bar - `--bg` at 92% + a strong 40px
* blur, edge to edge. Exported as the single source of truth so the mobile
* dropdown sheet ({@link MobileNav}) wears the exact same glass as the bar and the
* two can never drift.
*/
export const NAVBAR_GLASS_SURFACE =
'bg-[color-mix(in_srgb,var(--bg)_92%,transparent)] backdrop-blur-2xl'
export const NAVBAR_GLASS_SURFACE = cn(colorMixFallbacks.navbarGlass, 'backdrop-blur-2xl')

interface NavbarFrostContextValue {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'
import styles from '@/app/(landing)/components/shared/code-window-graphic/code-window-graphic.module.css'
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'
import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics'

/** One inline run of code text inside a {@link CodeWindowGraphic} line. */
Expand Down Expand Up @@ -40,7 +41,7 @@ const SEGMENT_TONE_CLASS = {
} as const

/** Shared hairline ink for the window outline, header rule, and icon box. */
const OUTLINE_INK = 'border-[color:color-mix(in_srgb,var(--text-muted-inverse)_45%,transparent)]'
const OUTLINE_INK = colorMixFallbacks.inverseBorder45

/**
* The dark-tile outlined editor window shared by the platform pages' "from
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
.navbarGlass {
background-color: var(--bg);
}

.mobileBackdrop {
background-color: var(--text-primary);
opacity: 0.08;
}

.mutedText60 {
color: var(--text-muted);
}

.loaderOuter {
--thinking-loader-outer: var(--text-body);
}
Comment thread
waleedlatif1 marked this conversation as resolved.

.inverseBorder45 {
border-color: var(--text-muted-inverse);
}

.inverseBackground45 {
background-color: var(--text-muted-inverse);
}

.mutedBackground35 {
background-color: var(--text-muted);
}

.mutedBorder60 {
border-color: var(--text-muted);
}

.inverseBorder22 {
border-color: var(--text-muted-inverse);
}

.inverseBorder35 {
border-color: var(--text-muted-inverse);
}

.inverseBorder70 {
border-color: var(--text-muted-inverse);
}

.mutedStroke35 {
stroke: var(--text-muted);
}

.inverseStroke28 {
stroke: var(--text-muted-inverse);
}

.inverseStroke45 {
stroke: var(--text-muted-inverse);
}

@supports (color: color-mix(in srgb, red, blue)) {
.navbarGlass {
background-color: color-mix(in srgb, var(--bg) 92%, transparent);
}

.mobileBackdrop {
background-color: color-mix(in srgb, var(--text-primary) 8%, transparent);
opacity: 1;
}

.mutedText60 {
color: color-mix(in srgb, var(--text-muted) 60%, transparent);
}

.loaderOuter {
--thinking-loader-outer: color-mix(in srgb, var(--text-body) 76%, var(--white));
}

.inverseBorder45 {
border-color: color-mix(in srgb, var(--text-muted-inverse) 45%, transparent);
}

.inverseBackground45 {
background-color: color-mix(in srgb, var(--text-muted-inverse) 45%, transparent);
}

.mutedBackground35 {
background-color: color-mix(in srgb, var(--text-muted) 35%, transparent);
}

.mutedBorder60 {
border-color: color-mix(in srgb, var(--text-muted) 60%, transparent);
}

.inverseBorder22 {
border-color: color-mix(in srgb, var(--text-muted-inverse) 22%, transparent);
}

.inverseBorder35 {
border-color: color-mix(in srgb, var(--text-muted-inverse) 35%, transparent);
}

.inverseBorder70 {
border-color: color-mix(in srgb, var(--text-muted-inverse) 70%, transparent);
}

.mutedStroke35 {
stroke: color-mix(in srgb, var(--text-muted) 35%, transparent);
}

.inverseStroke28 {
stroke: color-mix(in srgb, var(--text-muted-inverse) 28%, transparent);
}

.inverseStroke45 {
stroke: color-mix(in srgb, var(--text-muted-inverse) 45%, transparent);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use client'

import { type CSSProperties, useEffect, useRef, useState } from 'react'
import { type CSSProperties, type RefObject, useEffect, useRef, useState } from 'react'
import { chipBorderShadowRing, cn } from '@sim/emcn'
import dynamic from 'next/dynamic'
import { preconnect } from 'react-dom'
import { DemoForm, type DemoLead } from '@/app/(landing)/demo/components/demo-form'
import { applyLegacyInertFallback } from '@/app/(landing)/demo/components/legacy-inert-fallback'

const importScheduler = () => import('@/app/(landing)/demo/components/demo-scheduler')

Expand Down Expand Up @@ -32,6 +33,14 @@ const DemoScheduler = dynamic(() => importScheduler().then((m) => m.DemoSchedule
loading: () => null,
})

function useLegacyInertFallback(ref: RefObject<HTMLDivElement | null>, inert: boolean) {
useEffect(() => {
const node = ref.current
if (!inert || !node || 'inert' in HTMLElement.prototype) return
return applyLegacyInertFallback(node)
Comment thread
waleedlatif1 marked this conversation as resolved.
}, [inert, ref])
}

interface DemoBookingProps {
/** Layout/placement classes (grid cell). Never chrome. */
className?: string
Expand Down Expand Up @@ -59,8 +68,13 @@ export function DemoBooking({ className }: DemoBookingProps) {
const [lead, setLead] = useState<DemoLead | null>(null)
const [formHeight, setFormHeight] = useState<number>()
const formRef = useRef<HTMLDivElement>(null)
const formPanelRef = useRef<HTMLDivElement>(null)
const schedulerPanelRef = useRef<HTMLDivElement>(null)
const showScheduler = lead !== null

useLegacyInertFallback(formPanelRef, showScheduler)
useLegacyInertFallback(schedulerPanelRef, !showScheduler)

useEffect(() => {
const node = formRef.current
if (!node) return
Expand All @@ -87,6 +101,7 @@ export function DemoBooking({ className }: DemoBookingProps) {
style={{ transform: showScheduler ? 'translateX(-100%)' : undefined }}
>
<div
ref={formPanelRef}
className='w-full min-w-0 shrink-0'
inert={showScheduler}
onFocusCapture={() => void preloadScheduler()}
Expand All @@ -95,7 +110,11 @@ export function DemoBooking({ className }: DemoBookingProps) {
<DemoForm onComplete={setLead} />
</div>
</div>
<div className='h-full w-full min-w-0 shrink-0' inert={!showScheduler}>
<div
ref={schedulerPanelRef}
className='h-full w-full min-w-0 shrink-0'
inert={!showScheduler}
>
{lead ? <DemoScheduler lead={lead} /> : null}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @vitest-environment jsdom
*/
import { describe, expect, it } from 'vitest'
import { applyLegacyInertFallback } from '@/app/(landing)/demo/components/legacy-inert-fallback'

describe('applyLegacyInertFallback', () => {
it('removes descendants from interaction and restores their exact prior state', () => {
const panel = document.createElement('div')
panel.setAttribute('aria-hidden', 'false')
panel.style.pointerEvents = 'auto'
panel.innerHTML = `
<button>Continue</button>
<a href="/demo" tabindex="2">Demo</a>
<input disabled />
`

const button = panel.querySelector('button')
const link = panel.querySelector('a')
const disabledInput = panel.querySelector('input')
const restore = applyLegacyInertFallback(panel)

expect(panel.getAttribute('aria-hidden')).toBe('true')
expect(panel.style.pointerEvents).toBe('none')
expect(button?.getAttribute('tabindex')).toBe('-1')
expect(link?.getAttribute('tabindex')).toBe('-1')
expect(disabledInput?.getAttribute('tabindex')).toBeNull()

restore()

expect(panel.getAttribute('aria-hidden')).toBe('false')
expect(panel.style.pointerEvents).toBe('auto')
expect(button?.getAttribute('tabindex')).toBeNull()
expect(link?.getAttribute('tabindex')).toBe('2')
})

it('moves focus out of a panel before hiding it', () => {
const panel = document.createElement('div')
const button = document.createElement('button')
panel.append(button)
document.body.append(panel)
button.focus()

expect(document.activeElement).toBe(button)

const restore = applyLegacyInertFallback(panel)

expect(document.activeElement).not.toBe(button)

restore()
panel.remove()
})
})
Loading
Loading