update position page design

This commit is contained in:
2026-08-13 21:41:14 +05:30
parent 6340962907
commit 1c653264b5
124 changed files with 5746 additions and 596 deletions

View File

@@ -12,11 +12,11 @@ import { BarChart3 } from 'lucide-react';
* the brand values are mirrored here — this array is the only place a chart
* should get a colour from.
*/
export const CHART_COLORS = ['#0A39DF', '#F9E547', '#333F48', '#F8E08E', '#D1E0D7', '#082EB4'];
export const CHART_COLORS = ['#0838E0', '#F9E547', '#333F48', '#F8E08E', '#D1E0D7', '#062BAF'];
export const CHART_TONES = {
brand: '#0A39DF',
brandDark: '#082EB4',
brand: '#0838E0',
brandDark: '#062BAF',
accent: '#F9E547',
accentPale: '#F8E08E',
navy: '#333F48',

View File

@@ -115,7 +115,7 @@ export function ProgressRing({
const resolvedTone = tone === 'score' ? toneForScore(ratio * 100) : tone;
const strokeColor = {
brand: '#0A39DF',
brand: '#0838E0',
navy: '#333F48',
accent: '#F9E547',
success: 'hsl(142 71% 36%)',

View File

@@ -43,7 +43,7 @@ const surfaceVariants = cva(
lg: 'p-6',
},
interactive: {
true: 'cursor-pointer hover:shadow-lg hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#0A39DF]/60 focus-visible:ring-offset-2',
true: 'cursor-pointer hover:shadow-lg hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#0838E0]/60 focus-visible:ring-offset-2',
},
},
defaultVariants: {

View File

@@ -74,35 +74,67 @@ export function Toggle({
* 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 }) {
/**
* Two treatments for the same control.
*
* `brand` states a choice that changes what the data *says* — a date range, a
* scope — so the selection carries the page's accent colour and reads as a
* filter. `surface` states a choice that changes only how the same data is
* *drawn*, so the selection is a raised white chip on a sunken track: present
* enough to show which mode is on, quiet enough not to compete with the real
* filters sitting beside it in the same toolbar.
*/
const SEGMENT_VARIANTS = {
brand: {
track: 'bg-surface-sunken border-border',
active: 'bg-krow-blue text-white shadow-sm',
inactive: 'text-ink-3 hover:text-ink-1',
},
surface: {
track: 'bg-surface-sunken border-border',
active: 'bg-surface text-ink-1 shadow-xs',
inactive: 'text-ink-4 hover:text-ink-2',
},
};
export function SegmentedToggle({
options = [], value, onChange, size = 'default', variant = 'brand', className, ariaLabel,
}) {
const normalized = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o));
const tone = SEGMENT_VARIANTS[variant] || SEGMENT_VARIANTS.brand;
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
)}
className={cn('inline-flex items-center gap-0.5 rounded-full border p-0.5', tone.track, className)}
>
{normalized.map((option) => {
const active = option.value === value;
const Icon = option.icon;
/* Icon-only options carry their name on the button instead of beside
it, so the control stays compact without becoming unlabelled. */
const iconOnly = Boolean(Icon) && !option.label;
return (
<button
key={option.value}
type="button"
role="radio"
aria-checked={active}
aria-label={option.ariaLabel || (iconOnly ? String(option.value) : undefined)}
title={option.ariaLabel}
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'
'inline-flex items-center justify-center gap-1.5 rounded-full font-medium',
'transition-all duration-base ease-out focus-ring-inset',
iconOnly
? (size === 'sm' ? 'h-6 w-7' : 'h-7 w-8')
: (size === 'sm' ? 'px-2.5 py-1 text-caption' : 'px-3 py-1.5 text-body-sm'),
active ? tone.active : tone.inactive
)}
>
{Icon && <Icon className={size === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'} aria-hidden="true" />}
{option.label}
</button>
);