Files
krow-demo/src/components/ds/Toggle.jsx
2026-08-07 23:53:15 +05:30

113 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}