Chart Range Control
Shared accessible range and period control for BoardCN chart cards.
Control
The shared range control chart cards use.
function ChartRangeControlDemo() {
return <ChartRangeControl ranges={RANGES} value="7d" onValueChange={() => undefined} />;
}function ChartRangeControlDemo() {
return <ChartRangeControl ranges={RANGES} value="7d" onValueChange={() => undefined} />;
}Installation
npx shadcn@latest add @boardcn/chart-range-controlnpx shadcn@latest add @boardcn/chart-range-controlnpm packages
- @remixicon/react
BoardCN dependencies
The CLI installs these for you — you do not need to add them yourself.
Source
The file the CLI copies into your project.
"use client";
import { useState } from "react";
import { RiArrowDownSLine, RiCalendarLine, RiCheckLine } from "@remixicon/react";
import {
Dropdown,
DropdownItem,
DropdownPopover,
DropdownTrigger,
} from "@/components/base/dropdown/dropdown";
import { cx } from "@/utils/cx";
export interface ChartRangeOption {
id: string;
label: string;
}
export interface ChartRangeControlProps<T extends ChartRangeOption> {
/** Available periods. With no periods, `label` is rendered as a static pill. */
ranges?: readonly T[];
value?: string;
onValueChange?: (id: string) => void;
label?: string;
className?: string;
/** Area charts use BoardCN's narrower, truncating date-range treatment. */
variant?: "default" | "compact";
}
const STATIC_CLASS =
"flex h-8 shrink-0 items-center gap-1.5 rounded-2lg border border-border-button-default bg-background-primary-default pr-2.5 pl-2 shadow-xs";
const TRIGGER_CLASS =
"flex h-8 shrink-0 items-center gap-1.5 rounded-2lg border border-border-button-default bg-background-primary-default pr-1.5 pl-2 shadow-xs transition-colors duration-150 ease-out hover:bg-background-primary-hover";
const COMPACT_STATIC_CLASS =
"inline-flex h-8 shrink-0 items-center gap-1.5 rounded-2lg border border-border-button-default bg-background-primary-default px-2 text-body-medium text-text-primary shadow-xs";
const COMPACT_TRIGGER_CLASS =
"inline-flex h-8 max-w-[164px] shrink-0 items-center gap-1.5 rounded-2lg border border-border-button-default bg-background-primary-default px-2 text-body-medium text-text-primary shadow-xs";
/**
* Shared BoardCN chart period control. The dropdown implementation owns open
* state so the chevron, dismissal, and close-on-select behavior cannot drift
* between chart cards. Keyboard focus and activation are provided by the
* shared Dropdown primitives.
*/
export function ChartRangeControl<T extends ChartRangeOption>({
ranges,
value,
onValueChange,
label,
className,
variant = "default",
}: ChartRangeControlProps<T>) {
const [open, setOpen] = useState(false);
const selected = ranges?.find((item) => item.id === value) ?? ranges?.[0];
const displayLabel = selected?.label ?? label;
const compact = variant === "compact";
if (!ranges?.length) {
if (!displayLabel) return null;
const Tag = compact ? "span" : "div";
return (
<Tag className={cx(compact ? COMPACT_STATIC_CLASS : STATIC_CLASS, className)}>
<RiCalendarLine
className={cx(
"size-4 shrink-0",
compact ? "text-foreground-icon-secondary" : "text-text-secondary",
)}
aria-hidden
/>
<span className={compact ? "truncate" : "text-body-medium whitespace-nowrap text-text-primary"}>
{displayLabel}
</span>
</Tag>
);
}
return (
<Dropdown isOpen={open} onOpenChange={setOpen}>
<DropdownTrigger
aria-label={compact ? `Date range: ${displayLabel}` : "Change period"}
className={cx(compact ? COMPACT_TRIGGER_CLASS : TRIGGER_CLASS, className)}
>
<RiCalendarLine
className={cx(
"size-4 shrink-0",
compact ? "text-foreground-icon-secondary" : "text-text-secondary",
)}
aria-hidden
/>
<span className={compact ? "truncate" : "text-body-medium whitespace-nowrap text-text-primary"}>
{displayLabel}
</span>
<RiArrowDownSLine
data-range-chevron
className={cx(
"size-4 shrink-0 transition-transform duration-200 ease-out motion-reduce:transition-none",
!compact && "text-text-secondary",
open && "rotate-180",
)}
aria-hidden
/>
</DropdownTrigger>
<DropdownPopover
aria-label={compact ? "Select date range" : "Period"}
placement={compact ? undefined : "bottom end"}
className={compact ? "w-[188px]" : "w-[188px] p-2"}
dialogClassName={compact ? undefined : "gap-1"}
>
{ranges.map((item) => {
const isSelected = item.id === selected?.id;
return (
<DropdownItem
key={item.id}
selected={isSelected}
className={cx("px-2 py-1.5", compact && "justify-between text-body-medium")}
onSelect={() => {
onValueChange?.(item.id);
setOpen(false);
}}
>
<span className={compact ? undefined : "flex-1 text-body-medium"}>{item.label}</span>
{isSelected && (
<RiCheckLine
className={cx("size-4 shrink-0", !compact && "text-text-secondary")}
aria-hidden
/>
)}
</DropdownItem>
);
})}
</DropdownPopover>
</Dropdown>
);
}"use client";
import { useState } from "react";
import { RiArrowDownSLine, RiCalendarLine, RiCheckLine } from "@remixicon/react";
import {
Dropdown,
DropdownItem,
DropdownPopover,
DropdownTrigger,
} from "@/components/base/dropdown/dropdown";
import { cx } from "@/utils/cx";
export interface ChartRangeOption {
id: string;
label: string;
}
export interface ChartRangeControlProps<T extends ChartRangeOption> {
/** Available periods. With no periods, `label` is rendered as a static pill. */
ranges?: readonly T[];
value?: string;
onValueChange?: (id: string) => void;
label?: string;
className?: string;
/** Area charts use BoardCN's narrower, truncating date-range treatment. */
variant?: "default" | "compact";
}
const STATIC_CLASS =
"flex h-8 shrink-0 items-center gap-1.5 rounded-2lg border border-border-button-default bg-background-primary-default pr-2.5 pl-2 shadow-xs";
const TRIGGER_CLASS =
"flex h-8 shrink-0 items-center gap-1.5 rounded-2lg border border-border-button-default bg-background-primary-default pr-1.5 pl-2 shadow-xs transition-colors duration-150 ease-out hover:bg-background-primary-hover";
const COMPACT_STATIC_CLASS =
"inline-flex h-8 shrink-0 items-center gap-1.5 rounded-2lg border border-border-button-default bg-background-primary-default px-2 text-body-medium text-text-primary shadow-xs";
const COMPACT_TRIGGER_CLASS =
"inline-flex h-8 max-w-[164px] shrink-0 items-center gap-1.5 rounded-2lg border border-border-button-default bg-background-primary-default px-2 text-body-medium text-text-primary shadow-xs";
/**
* Shared BoardCN chart period control. The dropdown implementation owns open
* state so the chevron, dismissal, and close-on-select behavior cannot drift
* between chart cards. Keyboard focus and activation are provided by the
* shared Dropdown primitives.
*/
export function ChartRangeControl<T extends ChartRangeOption>({
ranges,
value,
onValueChange,
label,
className,
variant = "default",
}: ChartRangeControlProps<T>) {
const [open, setOpen] = useState(false);
const selected = ranges?.find((item) => item.id === value) ?? ranges?.[0];
const displayLabel = selected?.label ?? label;
const compact = variant === "compact";
if (!ranges?.length) {
if (!displayLabel) return null;
const Tag = compact ? "span" : "div";
return (
<Tag className={cx(compact ? COMPACT_STATIC_CLASS : STATIC_CLASS, className)}>
<RiCalendarLine
className={cx(
"size-4 shrink-0",
compact ? "text-foreground-icon-secondary" : "text-text-secondary",
)}
aria-hidden
/>
<span className={compact ? "truncate" : "text-body-medium whitespace-nowrap text-text-primary"}>
{displayLabel}
</span>
</Tag>
);
}
return (
<Dropdown isOpen={open} onOpenChange={setOpen}>
<DropdownTrigger
aria-label={compact ? `Date range: ${displayLabel}` : "Change period"}
className={cx(compact ? COMPACT_TRIGGER_CLASS : TRIGGER_CLASS, className)}
>
<RiCalendarLine
className={cx(
"size-4 shrink-0",
compact ? "text-foreground-icon-secondary" : "text-text-secondary",
)}
aria-hidden
/>
<span className={compact ? "truncate" : "text-body-medium whitespace-nowrap text-text-primary"}>
{displayLabel}
</span>
<RiArrowDownSLine
data-range-chevron
className={cx(
"size-4 shrink-0 transition-transform duration-200 ease-out motion-reduce:transition-none",
!compact && "text-text-secondary",
open && "rotate-180",
)}
aria-hidden
/>
</DropdownTrigger>
<DropdownPopover
aria-label={compact ? "Select date range" : "Period"}
placement={compact ? undefined : "bottom end"}
className={compact ? "w-[188px]" : "w-[188px] p-2"}
dialogClassName={compact ? undefined : "gap-1"}
>
{ranges.map((item) => {
const isSelected = item.id === selected?.id;
return (
<DropdownItem
key={item.id}
selected={isSelected}
className={cx("px-2 py-1.5", compact && "justify-between text-body-medium")}
onSelect={() => {
onValueChange?.(item.id);
setOpen(false);
}}
>
<span className={compact ? undefined : "flex-1 text-body-medium"}>{item.label}</span>
{isSelected && (
<RiCheckLine
className={cx("size-4 shrink-0", !compact && "text-text-secondary")}
aria-hidden
/>
)}
</DropdownItem>
);
})}
</DropdownPopover>
</Dropdown>
);
}Props
Generated from the component's TypeScript types. Standard DOM and React Aria props are omitted.
ChartRangeControl
Shared BoardCN chart period control. The dropdown implementation owns open state so the chevron, dismissal, and close-on-select behavior cannot drift between chart cards. Keyboard focus and activation are provided by the shared Dropdown primitives.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | — |
| label | string | — | — |
| onValueChange | (id: string) => void | — | — |
| ranges | readonly T[] | — | Available periods. With no periods, `label` is rendered as a static pill. |
| value | string | — | — |
| variant | "default" | "compact" | default | Area charts use BoardCN's narrower, truncating date-range treatment. |