fix error line
This commit is contained in:
@@ -11,6 +11,7 @@ import { StatusBadge } from '@/components/ds/StatusBadge';
|
||||
* `variant="card"`. Either way the anatomy is fixed — actor, sentence, meta,
|
||||
* trailing slot — so a feed built from mixed event types stays aligned.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function ActivityCard({
|
||||
/** Person or system that acted. */
|
||||
actor,
|
||||
|
||||
@@ -39,6 +39,7 @@ const ICON_COLORS = {
|
||||
* instead: an Alert that appears and disappears is easy to miss, and a toast
|
||||
* that persists is noise.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function Alert({ tone = 'info', title, children, icon, onDismiss, action, className, ...props }) {
|
||||
const Icon = icon || ICONS[tone];
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ export function initialsFrom(name = '') {
|
||||
* first-class state rather than a fallback afterthought. A failed image load
|
||||
* degrades to initials instead of a broken-image icon.
|
||||
*/
|
||||
/** @type {React.ForwardRefExoticComponent<any>} */
|
||||
export const Avatar = React.forwardRef(
|
||||
({ name, src, size = 'default', shape, tone, status, className, ...props }, ref) => {
|
||||
const [failed, setFailed] = React.useState(false);
|
||||
@@ -89,6 +90,7 @@ Avatar.displayName = 'Avatar';
|
||||
* AvatarGroup — overlapping avatars with a "+N" remainder, for showing several
|
||||
* people in a tight row (applicants on a position, endorsers on a skill).
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function AvatarGroup({ people = [], max = 4, size = 'sm', className }) {
|
||||
const visible = people.slice(0, max);
|
||||
const overflow = people.length - visible.length;
|
||||
|
||||
@@ -37,6 +37,7 @@ export const AXIS_PROPS = {
|
||||
* A tooltip matching the design system's surfaces. Recharts' default is a plain
|
||||
* white box that ignores the token set.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function ChartTooltip({ active, payload, label, valueFormatter, labelFormatter }) {
|
||||
if (!active || !payload?.length) return null;
|
||||
|
||||
@@ -97,6 +98,7 @@ export function ChartLegend({ items = [], className }) {
|
||||
* state, and an optional headline figure. Charts inside it are pure Recharts
|
||||
* children, so swapping a bar chart for a line chart changes nothing else.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function ChartContainer({
|
||||
title,
|
||||
subtitle,
|
||||
|
||||
@@ -23,6 +23,7 @@ import { usePreferences } from '@/lib/krowHooks';
|
||||
* `hideBelow: 'md'` drops a column on small screens, which is what keeps the
|
||||
* table usable on mobile without a horizontal scroll for every view.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function DataTable({
|
||||
columns = [],
|
||||
rows = [],
|
||||
|
||||
@@ -27,6 +27,7 @@ const WIDTHS = {
|
||||
* a detail view can be a Drawer on desktop and a bottom sheet on mobile without
|
||||
* its contents changing.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function Drawer({
|
||||
open,
|
||||
onOpenChange,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Button } from '@/components/ui/button';
|
||||
* Pages that conflate them end up telling a user with 22 candidates that they
|
||||
* have no candidates.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function EmptyState({
|
||||
icon: Icon,
|
||||
title,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { cn } from '@/lib/utils';
|
||||
* spacing, required markers and error styling are identical across the app.
|
||||
* It wires `id`, `aria-describedby` and `aria-invalid` onto its child.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function Field({
|
||||
label,
|
||||
hint,
|
||||
|
||||
@@ -18,24 +18,25 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
* On mobile the filters collapse behind a "Filters" toggle with an active count,
|
||||
* because four side-by-side selects are unusable at 375px.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function FilterBar({
|
||||
search,
|
||||
onSearchChange,
|
||||
search = null,
|
||||
onSearchChange = null,
|
||||
searchPlaceholder = 'Search...',
|
||||
|
||||
filters = [],
|
||||
/** `{ [key]: string | string[] }` */
|
||||
values = {},
|
||||
onChange,
|
||||
onChange = null,
|
||||
|
||||
/** Right-aligned actions — primary buttons, view switchers. */
|
||||
actions,
|
||||
actions = null,
|
||||
/** Rendered under the bar, e.g. a result count. */
|
||||
meta,
|
||||
meta = null,
|
||||
|
||||
/** Value that means "no filter" for select-type filters. */
|
||||
allValue = 'all',
|
||||
className,
|
||||
className = '',
|
||||
}) {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/comp
|
||||
* `label`: an icon-only control with no accessible name is a bug, so the API
|
||||
* makes it impossible to omit. The label doubles as the tooltip.
|
||||
*/
|
||||
/** @type {React.ForwardRefExoticComponent<any>} */
|
||||
export const IconButton = React.forwardRef(
|
||||
({ icon: Icon, label, tooltip = true, size = 'default', side = 'top', ...props }, ref) => {
|
||||
const sizeMap = {
|
||||
|
||||
@@ -45,18 +45,19 @@ const SEVERITY = {
|
||||
},
|
||||
};
|
||||
|
||||
/** @param {any} props */
|
||||
export function InsightRow({
|
||||
severity = 'info',
|
||||
title,
|
||||
detail,
|
||||
/** Short right-aligned figure — a count, an age, a percentage. */
|
||||
metric,
|
||||
metricLabel,
|
||||
metric = null,
|
||||
metricLabel = null,
|
||||
/** Entity the item concerns, e.g. a position or candidate name. */
|
||||
entity,
|
||||
onClick,
|
||||
entity = null,
|
||||
onClick = null,
|
||||
actionLabel = 'Review',
|
||||
className,
|
||||
className = '',
|
||||
}) {
|
||||
const s = SEVERITY[severity] || SEVERITY.info;
|
||||
const Comp = onClick ? 'button' : 'div';
|
||||
@@ -105,6 +106,7 @@ export function InsightRow({
|
||||
}
|
||||
|
||||
/** The list container — dividers between rows, nothing else. */
|
||||
export function InsightList({ children, className }) {
|
||||
/** @param {any} props */
|
||||
export function InsightList({ children, className = '' }) {
|
||||
return <div className={cn('divide-y divide-border', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ const TONES = {
|
||||
* (a target, a breakdown, a sparkline) use `MetricCard`; keeping the two
|
||||
* separate stops KPI rows from drifting into inconsistent heights.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function KpiCard({
|
||||
icon: Icon,
|
||||
value,
|
||||
@@ -94,6 +95,7 @@ const formatDelta = (delta, suffix = '%') =>
|
||||
* `invertTrend` exists because "up" is not always good — time-to-hire rising is
|
||||
* bad, and colouring it green would be actively misleading.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function MetricCard({
|
||||
label,
|
||||
value,
|
||||
|
||||
@@ -18,6 +18,7 @@ const GAPS = { sm: 'gap-3', default: 'gap-4', lg: 'gap-6' };
|
||||
* `cols` is the count at the largest breakpoint; the intermediate steps are
|
||||
* chosen to avoid orphan cards (6 → 2/3/6 rather than 1/3/6).
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function Grid({ cols = 3, gap = 'default', className, children, ...props }) {
|
||||
const columns = {
|
||||
1: 'grid-cols-1',
|
||||
@@ -36,6 +37,7 @@ export function Grid({ cols = 3, gap = 'default', className, children, ...props
|
||||
}
|
||||
|
||||
/** Stack — vertical rhythm. `space` maps to the two spacings pages should use. */
|
||||
/** @param {any} props */
|
||||
export function Stack({ space = 'default', className, children, ...props }) {
|
||||
const spacing = {
|
||||
xs: 'space-y-2',
|
||||
@@ -56,6 +58,7 @@ export function Stack({ space = 'default', className, children, ...props }) {
|
||||
* PageSection — a titled region of a page. Pairs a SectionHeader-style label
|
||||
* with its content at consistent spacing.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function PageSection({ title, subtitle, actions, level = 'overline', className, children }) {
|
||||
return (
|
||||
<section className={cn('space-y-3', className)}>
|
||||
|
||||
@@ -5,7 +5,8 @@ import { cn } from '@/lib/utils';
|
||||
/**
|
||||
* Spinner — the only spinner in the system.
|
||||
*/
|
||||
export function Spinner({ size = 'default', className, label = 'Loading' }) {
|
||||
/** @param {any} props */
|
||||
export function Spinner({ size = 'default', className = '', label = 'Loading' }) {
|
||||
const sizes = { xs: 'w-3.5 h-3.5', sm: 'w-4 h-4', default: 'w-6 h-6', lg: 'w-8 h-8' };
|
||||
return (
|
||||
<Loader2
|
||||
@@ -23,7 +24,8 @@ export function Spinner({ size = 'default', className, label = 'Loading' }) {
|
||||
* Prefer `Skeleton*` when the shape *is* known: a skeleton that matches the
|
||||
* final layout prevents the content jump a spinner always causes.
|
||||
*/
|
||||
export function LoadingState({ message, size = 'default', className }) {
|
||||
/** @param {any} props */
|
||||
export function LoadingState({ message = '', size = 'default', className = '' }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -42,7 +44,8 @@ export function LoadingState({ message, size = 'default', className }) {
|
||||
* InlineLoading — a spinner and label on one line, for inside buttons rows,
|
||||
* card headers, or beside a control that is refreshing.
|
||||
*/
|
||||
export function InlineLoading({ message = 'Loading…', className }) {
|
||||
/** @param {any} props */
|
||||
export function InlineLoading({ message = 'Loading…', className = '' }) {
|
||||
return (
|
||||
<span className={cn('inline-flex items-center gap-2 text-body-sm text-ink-3', className)}>
|
||||
<Spinner size="sm" />
|
||||
@@ -55,7 +58,8 @@ export function InlineLoading({ message = 'Loading…', className }) {
|
||||
* LoadingOverlay — covers content that is being replaced, keeping layout
|
||||
* height stable so the page does not jump. Used by DataTable when refetching.
|
||||
*/
|
||||
export function LoadingOverlay({ show, message, className }) {
|
||||
/** @param {any} props */
|
||||
export function LoadingOverlay({ show = false, message = '', className = '' }) {
|
||||
if (!show) return null;
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -57,6 +57,7 @@ function Delta({ delta, label, invert }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {any} props */
|
||||
export function MetricStrip({ items = [], columns, className, loading = false }) {
|
||||
const count = columns ?? Math.min(items.length, 6);
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ const WIDTHS = {
|
||||
* body scroll pushes the title and the confirm button off-screen on a laptop;
|
||||
* this keeps both pinned and scrolls only the middle.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
/** @param {any} props */
|
||||
export function Modal({
|
||||
open,
|
||||
onOpenChange,
|
||||
@@ -82,6 +84,7 @@ export function Modal({
|
||||
* always for, and it labels the action with the verb ("Delete") rather than
|
||||
* "OK", so the button says what will happen.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function ConfirmModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
@@ -91,7 +94,7 @@ export function ConfirmModal({
|
||||
cancelLabel = 'Cancel',
|
||||
onConfirm,
|
||||
tone = 'destructive',
|
||||
icon,
|
||||
icon = null,
|
||||
busy = false,
|
||||
}) {
|
||||
return (
|
||||
|
||||
@@ -12,6 +12,7 @@ import { SearchInput } from '@/components/ds/SearchInput';
|
||||
* options a filter field appears, so short lists stay uncluttered and long ones
|
||||
* stay usable. Options may be strings or `{ value, label, count }`.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function MultiSelect({
|
||||
options = [],
|
||||
value = [],
|
||||
@@ -20,8 +21,8 @@ export function MultiSelect({
|
||||
/** Chips shown before collapsing into "+N more". */
|
||||
maxChips = 2,
|
||||
searchThreshold = 8,
|
||||
disabled,
|
||||
className,
|
||||
disabled = false,
|
||||
className = '',
|
||||
align = 'start',
|
||||
emptyMessage = 'No options',
|
||||
}) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { cn } from '@/lib/utils';
|
||||
* right-aligned action slot. Owning the entrance animation here is what keeps
|
||||
* page transitions feeling like one product.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function PageHeader({
|
||||
title,
|
||||
subtitle,
|
||||
|
||||
@@ -9,6 +9,12 @@ import { Button } from '@/components/ui/button';
|
||||
* Always keeps the first page, the last page, and a window around the current
|
||||
* one, so the control's width stays stable as the user pages through.
|
||||
*/
|
||||
/**
|
||||
* @param {number} current
|
||||
* @param {number} total
|
||||
* @param {number} [siblings]
|
||||
* @returns {(number | string)[]}
|
||||
*/
|
||||
export function buildPageRange(current, total, siblings = 1) {
|
||||
const totalSlots = siblings * 2 + 5;
|
||||
if (total <= totalSlots) return Array.from({ length: total }, (_, i) => i + 1);
|
||||
@@ -30,6 +36,7 @@ export function buildPageRange(current, total, siblings = 1) {
|
||||
* Pagination — page navigation with an optional result summary and page-size
|
||||
* control. Renders nothing when there is only one page.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function Pagination({
|
||||
page = 1,
|
||||
pageCount = 1,
|
||||
|
||||
@@ -34,6 +34,7 @@ export function toneForScore(score) {
|
||||
* `tone="score"` picks the colour from the value, which is what most usages in
|
||||
* this app want.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function ProgressBar({
|
||||
value,
|
||||
max = 100,
|
||||
@@ -94,6 +95,7 @@ export function ProgressBar({
|
||||
* score, via `min`/`max`, so the KROW ID gauge and a completion ring are the
|
||||
* same component.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function ProgressRing({
|
||||
value,
|
||||
min = 0,
|
||||
@@ -175,7 +177,8 @@ export function ProgressRing({
|
||||
* Completed steps show a check rather than their number, so "done" is readable
|
||||
* at a glance without relying on colour.
|
||||
*/
|
||||
export function ProgressSteps({ steps = [], current = 0, className, onStepClick }) {
|
||||
/** @param {any} props */
|
||||
export function ProgressSteps({ steps = [], current = 0, className = '', onStepClick = null }) {
|
||||
const normalized = steps.map((s) => (typeof s === 'string' ? { label: s } : s));
|
||||
|
||||
return (
|
||||
@@ -239,6 +242,7 @@ export function ProgressSteps({ steps = [], current = 0, className, onStepClick
|
||||
* ScoreMeter — a labelled bar row used in score breakdowns, where many
|
||||
* dimensions are listed with a weight and a value.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function ScoreMeter({ label, value, weight, tone = 'score', className }) {
|
||||
return (
|
||||
<div className={cn('space-y-1.5', className)}>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { inputVariants } from '@/components/ui/input';
|
||||
* without re-running expensive work, while the field itself stays instantly
|
||||
* responsive. Pass `debounce={0}` for immediate updates.
|
||||
*/
|
||||
/** @type {React.ForwardRefExoticComponent<any>} */
|
||||
export const SearchInput = React.forwardRef(
|
||||
(
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ import { cn } from '@/lib/utils';
|
||||
* - `card` — a card's own title row
|
||||
* - `section` — a page-level section title
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function SectionHeader({
|
||||
title,
|
||||
subtitle,
|
||||
|
||||
@@ -9,7 +9,8 @@ import { cn } from '@/lib/utils';
|
||||
* progress. The shimmer is disabled automatically under
|
||||
* `prefers-reduced-motion` (see index.css).
|
||||
*/
|
||||
export function Skeleton({ className, radius = 'md', ...props }) {
|
||||
/** @param {any} props */
|
||||
export function Skeleton({ className = '', radius = 'md', ...props }) {
|
||||
return (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -31,7 +32,8 @@ export function Skeleton({ className, radius = 'md', ...props }) {
|
||||
*/
|
||||
|
||||
/** Paragraph placeholder. The last line is short, as real text is. */
|
||||
export function SkeletonText({ lines = 3, className }) {
|
||||
/** @param {any} props */
|
||||
export function SkeletonText({ lines = 3, className = '' }) {
|
||||
return (
|
||||
<div className={cn('space-y-2', className)} role="status" aria-label="Loading content">
|
||||
{Array.from({ length: lines }).map((_, i) => (
|
||||
@@ -45,7 +47,8 @@ export function SkeletonText({ lines = 3, className }) {
|
||||
}
|
||||
|
||||
/** Matches KpiCard: icon tile, big number, caption. */
|
||||
export function SkeletonKpi({ className }) {
|
||||
/** @param {any} props */
|
||||
export function SkeletonKpi({ className = '' }) {
|
||||
return (
|
||||
<div className={cn('glass-card border border-white/60 rounded-2xl p-4 shadow-sm', className)}>
|
||||
<Skeleton className="w-10 h-10 rounded-xl mb-3" />
|
||||
@@ -56,7 +59,8 @@ export function SkeletonKpi({ className }) {
|
||||
}
|
||||
|
||||
/** Matches a list row: avatar, two lines of text, trailing value. */
|
||||
export function SkeletonRow({ className }) {
|
||||
/** @param {any} props */
|
||||
export function SkeletonRow({ className = '' }) {
|
||||
return (
|
||||
<div className={cn('flex items-center gap-3 py-3', className)}>
|
||||
<Skeleton className="w-9 h-9" radius="full" />
|
||||
@@ -70,7 +74,8 @@ export function SkeletonRow({ className }) {
|
||||
}
|
||||
|
||||
/** Matches a card in a grid: title, meta, stat row. */
|
||||
export function SkeletonCard({ className }) {
|
||||
/** @param {any} props */
|
||||
export function SkeletonCard({ className = '' }) {
|
||||
return (
|
||||
<div className={cn('glass-card border border-white/60 rounded-2xl p-5 shadow-sm', className)}>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
@@ -94,7 +99,8 @@ export function SkeletonCard({ className }) {
|
||||
}
|
||||
|
||||
/** Matches DataTable: header row plus n body rows. */
|
||||
export function SkeletonTable({ rows = 5, columns = 4, className }) {
|
||||
/** @param {any} props */
|
||||
export function SkeletonTable({ rows = 5, columns = 4, className = '' }) {
|
||||
return (
|
||||
<div className={cn('overflow-hidden', className)} role="status" aria-label="Loading table">
|
||||
<div className="flex items-center gap-4 px-4 py-3 border-b border-border bg-surface-subtle">
|
||||
@@ -114,7 +120,8 @@ export function SkeletonTable({ rows = 5, columns = 4, className }) {
|
||||
}
|
||||
|
||||
/** Matches ChartContainer: title, legend, plot area. */
|
||||
export function SkeletonChart({ className, height = 240 }) {
|
||||
/** @param {any} props */
|
||||
export function SkeletonChart({ className = '', height = 240 }) {
|
||||
return (
|
||||
<div className={cn('glass-card border border-white/60 rounded-2xl p-6 shadow-sm', className)}>
|
||||
<Skeleton className="h-3.5 w-40 mb-2" />
|
||||
@@ -129,7 +136,8 @@ export function SkeletonChart({ className, height = 240 }) {
|
||||
}
|
||||
|
||||
/** n SkeletonRows inside a surface — the common "loading a list" case. */
|
||||
export function SkeletonList({ rows = 5, className }) {
|
||||
/** @param {any} props */
|
||||
export function SkeletonList({ rows = 5, className = '' }) {
|
||||
return (
|
||||
<div className={cn('divide-y divide-border', className)} role="status" aria-label="Loading list">
|
||||
{Array.from({ length: rows }).map((_, i) => (
|
||||
|
||||
@@ -77,6 +77,7 @@ const DOT_COLOR = {
|
||||
* `dot` adds a leading indicator for dense rows where colour alone is doing too
|
||||
* much work; it also gives colour-blind users a second cue alongside the label.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function StatusBadge({ status, label, tone, dot = false, size = 'default', className, ...props }) {
|
||||
const entry = STATUS_MAP[status] || {};
|
||||
const resolvedTone = tone || entry.tone || 'neutral';
|
||||
|
||||
@@ -55,6 +55,7 @@ const surfaceVariants = cva(
|
||||
}
|
||||
);
|
||||
|
||||
/** @type {React.ForwardRefExoticComponent<any>} */
|
||||
const Surface = React.forwardRef(
|
||||
({ className, variant, elevation, radius, padding, interactive, as, ...props }, ref) => {
|
||||
const Comp = as || 'div';
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
* unique when two tab sets are on screen at once, otherwise the indicator
|
||||
* animates between them.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function Tabs({
|
||||
tabs = [],
|
||||
value,
|
||||
|
||||
@@ -20,7 +20,8 @@ const TONE_DOT = {
|
||||
* The connecting rail is drawn per item rather than as one absolute line, so a
|
||||
* timeline can be nested inside a scrolling card without the rail detaching.
|
||||
*/
|
||||
export function Timeline({ items = [], className, compact = false }) {
|
||||
/** @param {any} props */
|
||||
export function Timeline({ items = [], className = '', compact = false }) {
|
||||
return (
|
||||
<ol className={cn('relative', className)}>
|
||||
{items.map((item, i) => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Switch } from '@/components/ui/switch';
|
||||
* always used with, and makes the whole row a label so the hit target is the
|
||||
* full width rather than the 36px control.
|
||||
*/
|
||||
/** @param {any} props */
|
||||
export function Toggle({
|
||||
checked,
|
||||
onCheckedChange,
|
||||
@@ -97,8 +98,15 @@ const SEGMENT_VARIANTS = {
|
||||
},
|
||||
};
|
||||
|
||||
/** @param {any} props */
|
||||
export function SegmentedToggle({
|
||||
options = [], value, onChange, size = 'default', variant = 'brand', className, ariaLabel,
|
||||
options = [],
|
||||
value = null,
|
||||
onChange = null,
|
||||
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;
|
||||
|
||||
@@ -28,7 +28,7 @@ const BASE_STYLE = {
|
||||
const DURATIONS = { success: 2600, error: 4200, info: 3000, loading: Infinity };
|
||||
|
||||
const withDefaults = (options = {}) => ({
|
||||
position: 'bottom-center',
|
||||
position: /** @type {import('react-hot-toast').ToastPosition} */ ('bottom-center'),
|
||||
...options,
|
||||
style: { ...BASE_STYLE, ...(options.style || {}) },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user