Initial commit

This commit is contained in:
2026-08-07 23:53:15 +05:30
commit 90f14a73b2
222 changed files with 35000 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import { Switch } from '@/components/ui/switch';
/**
* Toggle — a labelled switch row for settings.
*
* Wraps the Switch primitive with the label/description layout it is almost
* always used with, and makes the whole row a label so the hit target is the
* full width rather than the 36px control.
*/
export function Toggle({
checked,
onCheckedChange,
label,
description,
disabled,
/** `end` puts the switch on the right (settings rows); `start` on the left. */
align = 'end',
className,
id: providedId,
}) {
const reactId = React.useId();
const id = providedId || reactId;
const descriptionId = description ? `${id}-description` : undefined;
const control = (
<Switch
id={id}
checked={checked}
onCheckedChange={onCheckedChange}
disabled={disabled}
aria-describedby={descriptionId}
/>
);
const text = (
<span className="min-w-0">
<span className="block text-body-sm font-medium text-ink-2">{label}</span>
{description && (
<span id={descriptionId} className="block text-caption text-ink-3 mt-0.5">
{description}
</span>
)}
</span>
);
return (
<label
htmlFor={id}
className={cn(
'flex items-center gap-3 cursor-pointer select-none',
disabled && 'cursor-not-allowed opacity-60',
align === 'end' && 'justify-between',
className
)}
>
{align === 'start' ? (
<>
{control}
{text}
</>
) : (
<>
{text}
{control}
</>
)}
</label>
);
}
/**
* SegmentedToggle — a small pill group for switching between 2–4 mutually
* exclusive views. Used where Tabs would be too heavy (chart ranges, EN/ES).
*/
export function SegmentedToggle({ options = [], value, onChange, size = 'default', className, ariaLabel }) {
const normalized = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o));
return (
<div
role="radiogroup"
aria-label={ariaLabel}
className={cn(
'inline-flex items-center gap-0.5 rounded-full bg-surface-sunken p-0.5 border border-border',
className
)}
>
{normalized.map((option) => {
const active = option.value === value;
return (
<button
key={option.value}
type="button"
role="radio"
aria-checked={active}
onClick={() => onChange?.(option.value)}
className={cn(
'rounded-full font-medium transition-all duration-base ease-out focus-ring-inset',
size === 'sm' ? 'px-2.5 py-1 text-caption' : 'px-3 py-1.5 text-body-sm',
active
? 'bg-krow-blue text-white shadow-sm'
: 'text-ink-3 hover:text-ink-1'
)}
>
{option.label}
</button>
);
})}
</div>
);
}