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
15 changes: 15 additions & 0 deletions apps/web/src/components/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Progress } from '@/components/ui/progress'
import { hasFeature } from '@/lib/featureFlags'
import { useKonstaToggle } from '@/hooks/useKonstaOverride'
import {
CaretLeft,
GithubLogo,
Expand Down Expand Up @@ -105,6 +106,7 @@ const METICULOUS_ADDON_UPDATE_SNIPPET = 'docker exec -it meticai bash -lc "cd /a

export function SettingsView({ onBack, showBlobs, onToggleBlobs, isDark, isFollowSystem, onToggleTheme, onSetFollowSystem, platformTheme, onSetPlatformTheme }: SettingsViewProps) {
const { t } = useTranslation()
const { enabled: useKonstaUi, setEnabled: setUseKonstaUi } = useKonstaToggle()

const [settings, setSettings] = useState<Settings>({
geminiApiKey: '',
Expand Down Expand Up @@ -1166,6 +1168,19 @@ export function SettingsView({ onBack, showBlobs, onToggleBlobs, isDark, isFollo
</div>
)}

{/* Konsta UI toggle */}
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="konsta-ui-toggle" className="text-sm font-medium">{t('appearance.useKonstaUi')}</Label>
<p className="text-xs text-muted-foreground">{t('appearance.useKonstaUiDescription')}</p>
</div>
<Switch
id="konsta-ui-toggle"
checked={useKonstaUi}
onCheckedChange={setUseKonstaUi}
/>
</div>

</div>
)}

Expand Down
35 changes: 34 additions & 1 deletion apps/web/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ComponentProps } from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { Button as KButton } from 'konsta/react'
import { useKonstaOverride } from '@/hooks/useKonstaOverride'

import { cn } from "@/lib/utils"

Expand Down Expand Up @@ -43,7 +45,7 @@ const buttonVariants = cva(
}
)

function Button({
function ShadcnButton({
className,
variant,
size,
Expand All @@ -64,4 +66,35 @@ function Button({
)
}

function Button(props: ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) {
const useKonsta = useKonstaOverride()
const { variant, size, asChild = false, className, children, ...rest } = props

if (!useKonsta || size === 'icon' || asChild) {
return <ShadcnButton {...props} />
}

const isOutline = variant === 'outline'
const isClear = variant === 'ghost' || variant === 'link'
const isTonal = variant === 'ember' || variant === 'secondary'
const isRounded = variant === 'liquid' || variant === 'dark-brew' || variant === 'frosted' || variant === 'ember'
const isSmall = size === 'sm'
const isLarge = size === 'lg'

return (
<KButton
outline={isOutline}
clear={isClear}
tonal={isTonal}
rounded={isRounded}
small={isSmall}
large={isLarge}
className={className}
{...rest}
>
{children}
</KButton>
)
}

export { Button, buttonVariants }
11 changes: 10 additions & 1 deletion apps/web/src/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ComponentProps } from "react"
import { Card as KCard } from 'konsta/react'
import { useKonstaOverride } from '@/hooks/useKonstaOverride'

import { cn } from "@/lib/utils"

function Card({ className, ...props }: ComponentProps<"div">) {
function ShadcnCard({ className, ...props }: ComponentProps<"div">) {
return (
<div
data-slot="card"
Expand All @@ -15,6 +17,13 @@ function Card({ className, ...props }: ComponentProps<"div">) {
)
}

function Card({ className, ...props }: ComponentProps<"div">) {
const useKonsta = useKonstaOverride()
if (!useKonsta) return <ShadcnCard className={className} {...props} />
// contentWrap={false} because sub-components (CardHeader, CardContent, etc.) handle their own spacing
return <KCard outline contentWrap={false} className={cn("!p-0", className)} {...props} />
}

function CardHeader({ className, ...props }: ComponentProps<"div">) {
return (
<div
Expand Down
35 changes: 34 additions & 1 deletion apps/web/src/components/ui/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import { ComponentProps } from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Checkbox as KCheckbox } from 'konsta/react'
import CheckIcon from "lucide-react/dist/esm/icons/check"

import { useKonstaOverride } from '@/hooks/useKonstaOverride'
import { cn } from "@/lib/utils"

function Checkbox({
function ShadcnCheckbox({
className,
...props
}: ComponentProps<typeof CheckboxPrimitive.Root>) {
Expand All @@ -29,4 +31,35 @@ function Checkbox({
)
}

function Checkbox({
className,
...props
}: ComponentProps<typeof CheckboxPrimitive.Root>) {
const useKonsta = useKonstaOverride()

if (!useKonsta) {
return <ShadcnCheckbox className={className} {...props} />
}

const { checked, onCheckedChange, disabled, name, ...rest } = props
// Forward id, aria-*, data-* attributes for accessibility
const forwardedProps: Record<string, unknown> = {}
for (const [key, val] of Object.entries(rest)) {
if (key === 'id' || key === 'value' || key.startsWith('aria-') || key.startsWith('data-')) {
forwardedProps[key] = val
}
}

return (
<KCheckbox
checked={checked === true}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onCheckedChange?.(e.target.checked)}
disabled={disabled}
name={name}
className={className}
{...forwardedProps}
/>
)
}

export { Checkbox }
10 changes: 9 additions & 1 deletion apps/web/src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ComponentProps } from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import XIcon from "lucide-react/dist/esm/icons/x"
import { useKonstaOverride } from '@/hooks/useKonstaOverride'

import { cn } from "@/lib/utils"

Expand Down Expand Up @@ -44,18 +45,25 @@ function DialogOverlay({
)
}

// CSS-only adaptation: Konsta Dialog uses a fundamentally different API (imperative
// title/content/buttons props) that doesn't map to Radix's declarative portal pattern.
// Instead, we enhance Radix DialogContent with mobile-friendly styling when Konsta is active.
function DialogContent({
className,
children,
...props
}: ComponentProps<typeof DialogPrimitive.Content>) {
const useKonsta = useKonstaOverride()

return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
useKonsta
? "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-2xl border-0 p-6 shadow-xl duration-200 sm:max-w-lg"
: "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className
)}
{...props}
Expand Down
25 changes: 24 additions & 1 deletion apps/web/src/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ComponentProps } from "react"

import { useKonstaOverride } from '@/hooks/useKonstaOverride'
import { cn } from "@/lib/utils"

function Input({ className, type, ...props }: ComponentProps<"input">) {
function ShadcnInput({ className, type, ...props }: ComponentProps<"input">) {
return (
<input
type={type}
Expand All @@ -18,4 +19,26 @@ function Input({ className, type, ...props }: ComponentProps<"input">) {
)
}

function Input({ className, type, ...props }: ComponentProps<"input">) {
const useKonsta = useKonstaOverride()

if (!useKonsta) {
return <ShadcnInput className={className} type={type} {...props} />
}

return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-11 w-full min-w-0 rounded-lg border border-border/50 bg-transparent px-4 py-2.5 text-base shadow-none transition-all duration-200 outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
"focus-visible:border-primary focus-visible:ring-primary/20 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{...props}
/>
)
}

export { Input }
39 changes: 29 additions & 10 deletions apps/web/src/components/ui/progress.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import { ComponentProps } from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"

import { Progressbar } from 'konsta/react'
import { useKonstaOverride } from '@/hooks/useKonstaOverride'
import { cn } from "@/lib/utils"

function Progress({
className,
value,
...props
}: ComponentProps<typeof ProgressPrimitive.Root>) {
function ShadcnProgress({ className, value, ...props }: ComponentProps<typeof ProgressPrimitive.Root>) {
return (
<ProgressPrimitive.Root
data-slot="progress"
className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
className
)}
className={cn("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", className)}
{...props}
>
<ProgressPrimitive.Indicator
Expand All @@ -26,4 +20,29 @@ function Progress({
)
}

function Progress({ className, value, ...props }: ComponentProps<typeof ProgressPrimitive.Root>) {
const useKonsta = useKonstaOverride()

if (!useKonsta) {
return <ShadcnProgress className={className} value={value} {...props} />
}

// Konsta Progressbar expects 0-1, shadcn Progress uses 0-100
// Forward id, aria-*, and data-* attributes for accessibility
const { id, ...rest } = props as Record<string, unknown>
const forwardProps: Record<string, unknown> = {}
if (id) forwardProps.id = id
for (const [key, val] of Object.entries(rest)) {
if (key.startsWith('aria-') || key.startsWith('data-')) forwardProps[key] = val
}

return (
<Progressbar
progress={(value || 0) / 100}
className={className}
{...forwardProps}
/>
)
}

export { Progress }
7 changes: 6 additions & 1 deletion apps/web/src/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CheckIcon from "lucide-react/dist/esm/icons/check"
import ChevronDownIcon from "lucide-react/dist/esm/icons/chevron-down"
import ChevronUpIcon from "lucide-react/dist/esm/icons/chevron-up"

import { useKonstaOverride } from '@/hooks/useKonstaOverride'
import { cn } from "@/lib/utils"

function Select({
Expand Down Expand Up @@ -33,12 +34,16 @@ function SelectTrigger({
}: ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
const useKonsta = useKonstaOverride()

return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
useKonsta
? "border-border/50 data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-primary focus-visible:ring-primary/20 flex w-fit items-center justify-between gap-2 rounded-lg border bg-transparent px-3 py-2 text-base whitespace-nowrap shadow-none transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-11 data-[size=sm]:h-9 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
: "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
Expand Down
47 changes: 46 additions & 1 deletion apps/web/src/components/ui/slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import { ComponentProps, useMemo } from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { Range } from 'konsta/react'

import { useKonstaOverride } from '@/hooks/useKonstaOverride'
import { cn } from "@/lib/utils"

function Slider({
function ShadcnSlider({
className,
defaultValue,
value,
Expand Down Expand Up @@ -60,4 +62,47 @@ function Slider({
)
}

function Slider({
className,
defaultValue,
value,
min = 0,
max = 100,
onValueChange,
onValueCommit,
step,
disabled,
...rest
}: ComponentProps<typeof SliderPrimitive.Root>) {
const useKonsta = useKonstaOverride()

if (!useKonsta) {
return <ShadcnSlider className={className} defaultValue={defaultValue} value={value} min={min} max={max} onValueChange={onValueChange} onValueCommit={onValueCommit} step={step} disabled={disabled} {...rest} />
}

const singleValue = Array.isArray(value) ? value[0] : (Array.isArray(defaultValue) ? defaultValue[0] : min)

// Forward id, aria-*, data-* attributes
const forwardedProps: Record<string, unknown> = {}
for (const [key, val] of Object.entries(rest)) {
if (key === 'id' || key.startsWith('aria-') || key.startsWith('data-')) {
forwardedProps[key] = val
}
}

return (
<Range
value={singleValue}
min={min}
max={max}
step={step ?? 1}
disabled={disabled}
onInput={(e: React.ChangeEvent<HTMLInputElement>) => onValueChange?.([Number(e.target.value)])}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onValueCommit?.([Number(e.target.value)])}
className={className}
Comment on lines +65 to +102
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Konsta mode, this Slider wrapper only triggers onValueChange, but the app uses onValueCommit (e.g. ControlCenterExpanded brightness slider). When Konsta UI is active, onValueCommit is ignored so that slider won’t call any handler at all.

Consider mapping Konsta Range events to both onValueChange and onValueCommit (or emulating commit on pointer/touch end), and forwarding the remaining props needed by current consumers (e.g. aria-* like aria-label, id, etc.).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 82ee815 — Slider now maps Konsta Range onInput→onValueChange (continuous) and onChange→onValueCommit (on release). Also forwards id, aria-, data- props.

{...forwardedProps}
/>
)
}

export { Slider }
Loading
Loading