update position page design
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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%)',
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user