@upbound/monarch-blocks 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/filter-bar/filter-bar.tsx","../src/lib/utils.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { type DateRange } from 'react-day-picker';\nimport { Icon } from '@upbound/monarch-core';\n\nimport { cn } from '@/lib/utils';\nimport { Badge } from '@upbound/monarch-core';\nimport { Button } from '@upbound/monarch-core';\nimport { ButtonGroup, ButtonGroupText } from '@upbound/monarch-core';\nimport { Calendar } from '@upbound/monarch-core';\nimport {\n Combobox,\n ComboboxChip,\n ComboboxClearAll,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxSelectedChips,\n ComboboxSeparator,\n ComboboxTrigger,\n ComboboxValue,\n} from '@upbound/monarch-core';\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n} from '@upbound/monarch-core';\nimport { Input } from '@upbound/monarch-core';\nimport { Label } from '@upbound/monarch-core';\nimport { Popover, PopoverContent, PopoverTrigger } from '@upbound/monarch-core';\n\n// ── Types ─────────────────────────────────────────────\n\ntype FilterType = 'checkbox' | 'radio' | 'text' | 'multiText' | 'date';\n\ninterface FilterOption {\n value: string;\n label: string;\n icon?: React.ReactNode;\n}\n\ninterface FilterDefinitionBase {\n key: string;\n label: string;\n}\n\ninterface CheckboxFilterDefinition extends FilterDefinitionBase {\n type?: 'checkbox';\n options: FilterOption[];\n /**\n * When `true`, the chip's value popup becomes a searchable Combobox\n * (multi-select with a search input). Use for long option lists where a\n * basic dropdown becomes hard to scan. When `false` or omitted, the chip\n * uses the standard checkbox dropdown.\n */\n searchable?: boolean;\n}\n\ninterface RadioFilterDefinition extends FilterDefinitionBase {\n type: 'radio';\n options: FilterOption[];\n /** Same searchable-Combobox swap as `CheckboxFilterDefinition`, constrained to a single selection. */\n searchable?: boolean;\n}\n\ninterface TextFilterDefinition extends FilterDefinitionBase {\n type: 'text';\n placeholder?: string;\n}\n\ninterface MultiTextFilterDefinition extends FilterDefinitionBase {\n type: 'multiText';\n placeholder?: string;\n}\n\ninterface DateFilterDefinition extends FilterDefinitionBase {\n type: 'date';\n /**\n * `true` always shows Start/End time inputs, `false` never shows them,\n * and omitted auto-detects from whether the current value already carries\n * a non-midnight time component.\n */\n includeTime?: boolean;\n}\n\ntype FilterDefinition =\n | CheckboxFilterDefinition\n | RadioFilterDefinition\n | TextFilterDefinition\n | MultiTextFilterDefinition\n | DateFilterDefinition;\n\ntype CheckboxFilterValue = string[];\ntype RadioFilterValue = string;\ntype TextFilterValue = string;\ntype MultiTextFilterValue = string[];\ntype DateFilterValue = { startDate: Date | null; endDate: Date | null };\ntype FilterValue = CheckboxFilterValue | RadioFilterValue | TextFilterValue | MultiTextFilterValue | DateFilterValue;\n\ninterface ActiveFilter {\n key: string;\n value?: FilterValue;\n}\n\n// ── Value helpers ─────────────────────────────────────\n\nfunction asCheckboxValue(value: FilterValue | undefined): CheckboxFilterValue {\n return Array.isArray(value) ? value : [];\n}\n\nfunction asRadioValue(value: FilterValue | undefined): RadioFilterValue {\n return typeof value === 'string' ? value : '';\n}\n\nfunction asTextValue(value: FilterValue | undefined): TextFilterValue {\n return typeof value === 'string' ? value : '';\n}\n\nfunction asMultiTextValue(value: FilterValue | undefined): MultiTextFilterValue {\n return Array.isArray(value) ? value : [];\n}\n\nfunction asDateValue(value: FilterValue | undefined): DateFilterValue {\n if (value && typeof value === 'object' && !Array.isArray(value)) return value as DateFilterValue;\n return { startDate: null, endDate: null };\n}\n\nfunction hasExplicitTime(date: Date | null): boolean {\n if (!date) return false;\n return date.getHours() !== 0 || date.getMinutes() !== 0;\n}\n\nfunction formatTimeForInput(date: Date | null): string {\n if (!date) return '';\n return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;\n}\n\nfunction applyTimeToDate(date: Date, time: string): Date {\n const [hours, minutes] = time.split(':').map(Number);\n const next = new Date(date);\n next.setHours(hours || 0, minutes || 0, 0, 0);\n return next;\n}\n\nfunction formatDateLabel(date: Date): string {\n return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n}\n\n// ── FilterBar ─────────────────────────────────────────\n\ninterface FilterBarProps extends React.ComponentProps<'div'> {\n filters: FilterDefinition[];\n activeFilters: ActiveFilter[];\n onAddFilter: (key: string) => void;\n onRemoveFilter: (key: string) => void;\n onChangeFilter: (key: string, value: FilterValue) => void;\n}\n\nfunction FilterBar({\n filters,\n activeFilters,\n onAddFilter,\n onRemoveFilter,\n onChangeFilter,\n className,\n children,\n ...props\n}: FilterBarProps) {\n const usedKeys = new Set(activeFilters.map(f => f.key));\n const availableFilters = filters.filter(f => !usedKeys.has(f.key));\n const shouldShowFilters = availableFilters.length > 0 || activeFilters.length > 0;\n\n // Tracks the most recently added filter so its chip can open its value\n // popup immediately, without waiting for a second click.\n const [pendingOpenKey, setPendingOpenKey] = React.useState<string | null>(null);\n\n function handleAddFilter(key: string) {\n onAddFilter(key);\n setPendingOpenKey(key);\n }\n\n return (\n <div\n data-slot=\"filter-bar\"\n data-visible-filters={shouldShowFilters}\n className={cn('flex flex-wrap items-center gap-2', className)}\n {...props}\n >\n {/* Add filter button */}\n {availableFilters.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\">\n <Icon name=\"plus\" data-icon=\"inline-start\" />\n Add filter\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"start\"\n className=\"w-48\"\n // Picking a filter here immediately auto-opens its chip's own\n // value popup, which grabs focus for itself (autoFocus input, or\n // the popup's first item). Radix's default close behavior moves\n // focus back to this menu's trigger once its close animation\n // finishes — a hair after the new popup opens in a real browser\n // — which yanks focus out of that popup and, being non-modal,\n // makes it treat that as an outside interaction and dismiss\n // itself. Suppress the auto-return so the new popup keeps focus.\n onCloseAutoFocus={e => e.preventDefault()}\n >\n {availableFilters.map(filter => (\n <DropdownMenuItem key={filter.key} onSelect={() => handleAddFilter(filter.key)}>\n {filter.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n\n {/* Active filter chips */}\n {activeFilters.map(active => {\n const definition = filters.find(f => f.key === active.key);\n if (!definition) return null;\n return (\n <FilterChip\n key={active.key}\n definition={definition}\n value={active.value}\n autoOpen={active.key === pendingOpenKey}\n onChangeValue={value => onChangeFilter(active.key, value)}\n onRemove={() => onRemoveFilter(active.key)}\n />\n );\n })}\n\n {/* Children (count, actions) — sit immediately after the last chip\n with the same `gap-2` spacing. With the parent's `flex-wrap`, a\n long row of chips will push the action onto the next line instead\n of stretching across an empty horizontal void. */}\n {children}\n </div>\n );\n}\n\n// ── FilterChip (dispatcher) ────────────────────────────\n\ninterface FilterChipProps {\n definition: FilterDefinition;\n value: FilterValue | undefined;\n /** Opens the chip's value popup as soon as it mounts — used right after the filter is added. */\n autoOpen?: boolean;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}\n\nfunction FilterChip({ definition, value, autoOpen, onChangeValue, onRemove }: FilterChipProps) {\n const type = definition.type ?? 'checkbox';\n\n switch (type) {\n case 'checkbox':\n return (\n <CheckboxFilterChip\n definition={definition as CheckboxFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n case 'radio':\n return (\n <RadioFilterChip\n definition={definition as RadioFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n case 'text':\n return (\n <TextFilterChip\n definition={definition as TextFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n case 'multiText':\n return (\n <MultiTextFilterChip\n definition={definition as MultiTextFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n case 'date':\n return (\n <DateFilterChip\n definition={definition as DateFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n }\n}\n\n// ── Shared chip chrome ─────────────────────────────────\n\nfunction FilterChipRemoveButton({ label, onRemove }: { label: string; onRemove: () => void }) {\n return (\n <Button variant=\"outline\" size=\"icon\" aria-label={`Remove ${label} filter`} onClick={onRemove}>\n <Icon name=\"xmark\" />\n </Button>\n );\n}\n\n// ── Checkbox chip ──────────────────────────────────────\n\nfunction CheckboxFilterChip({\n definition,\n value,\n autoOpen,\n onChangeValue,\n onRemove,\n}: {\n definition: CheckboxFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, options, searchable = false } = definition;\n const values = asCheckboxValue(value);\n const displayLabel =\n values.length === 0\n ? 'Any'\n : values.length === 1\n ? (options.find(o => o.value === values[0])?.label ?? values[0])\n : `${values.length} selected`;\n\n function toggle(optionValue: string) {\n const next = values.includes(optionValue) ? values.filter(v => v !== optionValue) : [...values, optionValue];\n onChangeValue(next);\n }\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n {searchable ? (\n <Combobox\n items={options}\n itemToStringValue={(item: FilterOption) => item.label}\n multiple\n defaultOpen={autoOpen}\n value={options.filter(o => values.includes(o.value))}\n onValueChange={(selected: FilterOption[]) => onChangeValue(selected.map(s => s.value))}\n >\n <ComboboxTrigger\n render={<Button variant=\"outline\" className=\"gap-1 font-normal [&>svg:last-child]:size-(--icon-sm)\" />}\n >\n <span className={values.length === 0 ? 'text-muted-foreground' : ''}>{displayLabel}</span>\n </ComboboxTrigger>\n <ComboboxContent className=\"min-w-64\" collisionAvoidance={{ side: 'none', align: 'shift' }}>\n <ComboboxInput showTrigger={false} placeholder={`Search ${label.toLowerCase()}…`} />\n {values.length > 0 && (\n <>\n <ComboboxSelectedChips>\n <ComboboxValue>\n {(selected: FilterOption[]) => (\n <>\n {selected.map(item => (\n <ComboboxChip key={item.value}>{item.label}</ComboboxChip>\n ))}\n </>\n )}\n </ComboboxValue>\n <ComboboxClearAll onClick={() => onChangeValue([])} />\n </ComboboxSelectedChips>\n <ComboboxSeparator />\n </>\n )}\n <ComboboxEmpty>No {label.toLowerCase()} found.</ComboboxEmpty>\n <ComboboxList>\n {(item: FilterOption) => (\n <ComboboxItem key={item.value} value={item}>\n {item.icon}\n {item.label}\n </ComboboxItem>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n ) : (\n <DropdownMenu defaultOpen={autoOpen}>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={values.length === 0 ? 'text-muted-foreground' : ''}>{displayLabel}</span>\n <Icon name=\"chevron-down\" size=\"sm\" className=\"text-muted-foreground\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {options.map(opt => (\n <DropdownMenuCheckboxItem\n key={opt.value}\n checked={values.includes(opt.value)}\n onCheckedChange={() => toggle(opt.value)}\n >\n {opt.icon}\n {opt.label}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\n// ── Radio chip ─────────────────────────────────────────\n\nfunction RadioFilterChip({\n definition,\n value,\n autoOpen,\n onChangeValue,\n onRemove,\n}: {\n definition: RadioFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, options, searchable = false } = definition;\n const selectedValue = asRadioValue(value);\n const displayLabel = selectedValue ? (options.find(o => o.value === selectedValue)?.label ?? selectedValue) : 'Any';\n const selected = options.find(o => o.value === selectedValue) ?? null;\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n {searchable ? (\n <Combobox\n items={options}\n itemToStringValue={(item: FilterOption) => item.label}\n defaultOpen={autoOpen}\n value={selected}\n onValueChange={(next: FilterOption | null) => onChangeValue(next?.value ?? '')}\n >\n <ComboboxTrigger\n render={<Button variant=\"outline\" className=\"gap-1 font-normal [&>svg:last-child]:size-(--icon-sm)\" />}\n >\n <span className={selectedValue ? '' : 'text-muted-foreground'}>{displayLabel}</span>\n </ComboboxTrigger>\n <ComboboxContent className=\"min-w-64\" collisionAvoidance={{ side: 'none', align: 'shift' }}>\n <ComboboxInput showTrigger={false} placeholder={`Search ${label.toLowerCase()}…`} />\n <ComboboxEmpty>No {label.toLowerCase()} found.</ComboboxEmpty>\n <ComboboxList>\n {(item: FilterOption) => (\n <ComboboxItem key={item.value} value={item}>\n {item.icon}\n {item.label}\n </ComboboxItem>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n ) : (\n <DropdownMenu defaultOpen={autoOpen}>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={selectedValue ? '' : 'text-muted-foreground'}>{displayLabel}</span>\n <Icon name=\"chevron-down\" size=\"sm\" className=\"text-muted-foreground\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n <DropdownMenuRadioGroup value={selectedValue} onValueChange={onChangeValue}>\n {options.map(opt => (\n <DropdownMenuRadioItem key={opt.value} value={opt.value}>\n {opt.icon}\n {opt.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\n// ── Text chip ──────────────────────────────────────────\n\nfunction TextFilterChip({\n definition,\n value,\n autoOpen,\n onChangeValue,\n onRemove,\n}: {\n definition: TextFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, placeholder } = definition;\n const text = asTextValue(value);\n const [open, setOpen] = React.useState(!!autoOpen);\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={text ? '' : 'text-muted-foreground'}>{text || 'Any'}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\">\n <Input\n autoFocus\n value={text}\n placeholder={placeholder ?? 'Enter a value…'}\n onChange={e => onChangeValue(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter') setOpen(false);\n }}\n />\n </PopoverContent>\n </Popover>\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\n// ── MultiText chip ─────────────────────────────────────\n\nfunction MultiTextFilterChip({\n definition,\n value,\n autoOpen,\n onChangeValue,\n onRemove,\n}: {\n definition: MultiTextFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, placeholder } = definition;\n const tokens = asMultiTextValue(value);\n const [draft, setDraft] = React.useState('');\n const displayLabel = tokens.length === 0 ? 'Any' : tokens.length === 1 ? tokens[0] : `${tokens.length} values`;\n\n function addToken() {\n const token = draft.trim();\n if (!token || tokens.includes(token)) {\n setDraft('');\n return;\n }\n onChangeValue([...tokens, token]);\n setDraft('');\n }\n\n function removeToken(token: string) {\n onChangeValue(tokens.filter(t => t !== token));\n }\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n <Popover defaultOpen={autoOpen}>\n <PopoverTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={tokens.length === 0 ? 'text-muted-foreground' : ''}>{displayLabel}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\">\n <div className=\"flex flex-col gap-2\">\n {tokens.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {tokens.map(token => (\n <Badge key={token} variant=\"secondary\" className=\"gap-1\">\n {token}\n <button\n type=\"button\"\n aria-label={`Remove ${token}`}\n onClick={() => removeToken(token)}\n className=\"cursor-pointer\"\n >\n <Icon name=\"xmark\" size=\"xs\" />\n </button>\n </Badge>\n ))}\n </div>\n )}\n <Input\n autoFocus\n value={draft}\n placeholder={placeholder ?? 'Type a value, press Enter…'}\n onChange={e => setDraft(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter') {\n e.preventDefault();\n addToken();\n } else if (e.key === 'Backspace' && draft === '' && tokens.length > 0) {\n removeToken(tokens[tokens.length - 1]);\n }\n }}\n />\n </div>\n </PopoverContent>\n </Popover>\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\n// ── Date chip ──────────────────────────────────────────\n\nfunction DateFilterChip({\n definition,\n value,\n autoOpen,\n onChangeValue,\n onRemove,\n}: {\n definition: DateFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, includeTime } = definition;\n const { startDate, endDate } = asDateValue(value);\n const showTimeInputs = includeTime ?? (hasExplicitTime(startDate) || hasExplicitTime(endDate));\n\n const displayLabel = !startDate\n ? 'Any'\n : !endDate || startDate.getTime() === endDate.getTime()\n ? formatDateLabel(startDate)\n : `${formatDateLabel(startDate)} – ${formatDateLabel(endDate)}`;\n\n function pickRange(range: DateRange | undefined) {\n if (!range?.from) {\n onChangeValue({ startDate: null, endDate: null });\n return;\n }\n onChangeValue({ startDate: range.from, endDate: range.to ?? range.from });\n }\n\n function pickStartTime(time: string) {\n if (!startDate) return;\n onChangeValue({ startDate: applyTimeToDate(startDate, time), endDate });\n }\n\n function pickEndTime(time: string) {\n if (!endDate) return;\n onChangeValue({ startDate, endDate: applyTimeToDate(endDate, time) });\n }\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n <Popover defaultOpen={autoOpen}>\n <PopoverTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={startDate ? '' : 'text-muted-foreground'}>{displayLabel}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-fit\">\n <div className=\"flex flex-col gap-2\">\n <Calendar\n mode=\"range\"\n selected={{ from: startDate ?? undefined, to: endDate ?? undefined }}\n onSelect={pickRange}\n />\n {showTimeInputs && (\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex w-full items-center gap-2\">\n <Label\n htmlFor={`${definition.key}-start-time`}\n className=\"text-body-sm text-muted-foreground w-10 shrink-0\"\n >\n Start\n </Label>\n <Input\n id={`${definition.key}-start-time`}\n type=\"time\"\n className=\"w-full\"\n value={formatTimeForInput(startDate)}\n onChange={e => pickStartTime(e.target.value)}\n disabled={!startDate}\n />\n </div>\n <div className=\"flex w-full items-center gap-2\">\n <Label\n htmlFor={`${definition.key}-end-time`}\n className=\"text-body-sm text-muted-foreground w-10 shrink-0\"\n >\n End\n </Label>\n <Input\n id={`${definition.key}-end-time`}\n type=\"time\"\n className=\"w-full\"\n value={formatTimeForInput(endDate)}\n onChange={e => pickEndTime(e.target.value)}\n disabled={!endDate}\n />\n </div>\n </div>\n )}\n </div>\n </PopoverContent>\n </Popover>\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\nexport {\n FilterBar,\n FilterChip,\n type FilterType,\n type FilterDefinition,\n type CheckboxFilterDefinition,\n type RadioFilterDefinition,\n type TextFilterDefinition,\n type MultiTextFilterDefinition,\n type DateFilterDefinition,\n type ActiveFilter,\n type FilterOption,\n type FilterValue,\n type CheckboxFilterValue,\n type RadioFilterValue,\n type TextFilterValue,\n type MultiTextFilterValue,\n type DateFilterValue,\n type FilterBarProps,\n};\n","import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n {\n text: [\n 'display-hero',\n 'display-kpi-sm',\n 'display-kpi',\n 'display-kpi-lg',\n 'display-feature',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'body-lg',\n 'body',\n 'body-sm',\n 'caption',\n 'eyebrow',\n ],\n },\n ],\n },\n },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":"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","names":["_a"]}
1
+ {"version":3,"sources":["../src/filter-bar/filter-bar.tsx","../src/lib/utils.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { type DateRange } from 'react-day-picker';\nimport { Icon } from '@upbound/monarch-core';\n\nimport { cn, sortBy } from '@/lib/utils';\nimport { Badge } from '@upbound/monarch-core';\nimport { Button } from '@upbound/monarch-core';\nimport { ButtonGroup, ButtonGroupText } from '@upbound/monarch-core';\nimport { Calendar } from '@upbound/monarch-core';\nimport {\n Combobox,\n ComboboxChip,\n ComboboxClearAll,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxSelectedChips,\n ComboboxSeparator,\n ComboboxTrigger,\n ComboboxValue,\n} from '@upbound/monarch-core';\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n} from '@upbound/monarch-core';\nimport { Input } from '@upbound/monarch-core';\nimport { Label } from '@upbound/monarch-core';\nimport { Popover, PopoverContent, PopoverTrigger } from '@upbound/monarch-core';\n\n// ── Types ─────────────────────────────────────────────\n\ntype FilterType = 'checkbox' | 'radio' | 'text' | 'multiText' | 'date';\n\ninterface FilterOption {\n value: string;\n label: string;\n icon?: React.ReactNode;\n}\n\ninterface FilterDefinitionBase {\n key: string;\n label: string;\n}\n\ninterface CheckboxFilterDefinition extends FilterDefinitionBase {\n type?: 'checkbox';\n options: FilterOption[];\n /**\n * When `true`, the chip's value popup becomes a searchable Combobox\n * (multi-select with a search input). Use for long option lists where a\n * basic dropdown becomes hard to scan. When `false` or omitted, the chip\n * uses the standard checkbox dropdown.\n */\n searchable?: boolean;\n}\n\ninterface RadioFilterDefinition extends FilterDefinitionBase {\n type: 'radio';\n options: FilterOption[];\n /** Same searchable-Combobox swap as `CheckboxFilterDefinition`, constrained to a single selection. */\n searchable?: boolean;\n}\n\ninterface TextFilterDefinition extends FilterDefinitionBase {\n type: 'text';\n placeholder?: string;\n}\n\ninterface MultiTextFilterDefinition extends FilterDefinitionBase {\n type: 'multiText';\n placeholder?: string;\n}\n\ninterface DateFilterDefinition extends FilterDefinitionBase {\n type: 'date';\n /**\n * `true` always shows Start/End time inputs, `false` never shows them,\n * and omitted auto-detects from whether the current value already carries\n * a non-midnight time component.\n */\n includeTime?: boolean;\n}\n\ntype FilterDefinition =\n | CheckboxFilterDefinition\n | RadioFilterDefinition\n | TextFilterDefinition\n | MultiTextFilterDefinition\n | DateFilterDefinition;\n\ntype CheckboxFilterValue = string[];\ntype RadioFilterValue = string;\ntype TextFilterValue = string;\ntype MultiTextFilterValue = string[];\ntype DateFilterValue = { startDate: Date | null; endDate: Date | null };\ntype FilterValue = CheckboxFilterValue | RadioFilterValue | TextFilterValue | MultiTextFilterValue | DateFilterValue;\n\ninterface ActiveFilter {\n key: string;\n value?: FilterValue;\n}\n\n// ── Value helpers ─────────────────────────────────────\n\nfunction asCheckboxValue(value: FilterValue | undefined): CheckboxFilterValue {\n return Array.isArray(value) ? value : [];\n}\n\nfunction asRadioValue(value: FilterValue | undefined): RadioFilterValue {\n return typeof value === 'string' ? value : '';\n}\n\nfunction asTextValue(value: FilterValue | undefined): TextFilterValue {\n return typeof value === 'string' ? value : '';\n}\n\nfunction asMultiTextValue(value: FilterValue | undefined): MultiTextFilterValue {\n return Array.isArray(value) ? value : [];\n}\n\nfunction asDateValue(value: FilterValue | undefined): DateFilterValue {\n if (value && typeof value === 'object' && !Array.isArray(value)) return value as DateFilterValue;\n return { startDate: null, endDate: null };\n}\n\nfunction hasExplicitTime(date: Date | null): boolean {\n if (!date) return false;\n return date.getHours() !== 0 || date.getMinutes() !== 0;\n}\n\nfunction formatTimeForInput(date: Date | null): string {\n if (!date) return '';\n return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;\n}\n\nfunction applyTimeToDate(date: Date, time: string): Date {\n const [hours, minutes] = time.split(':').map(Number);\n const next = new Date(date);\n next.setHours(hours || 0, minutes || 0, 0, 0);\n return next;\n}\n\nfunction formatDateLabel(date: Date): string {\n return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n}\n\n/** Merges the internal ref FilterBar uses to track its own root node with\n * any ref a consumer passes to `FilterBar` itself. */\nfunction composeRefs<T>(...refs: Array<React.Ref<T> | null | undefined>) {\n return (node: T | null) => {\n for (const ref of refs) {\n if (typeof ref === 'function') ref(node);\n else if (ref) (ref as React.RefObject<T | null>).current = node;\n }\n };\n}\n\ntype PortalContainer = React.ComponentProps<typeof ComboboxContent>['container'];\n\n// ── FilterBar ─────────────────────────────────────────\n\ninterface FilterBarProps extends React.ComponentProps<'div'> {\n filters: FilterDefinition[];\n activeFilters: ActiveFilter[];\n onAddFilter: (key: string) => void;\n onRemoveFilter: (key: string) => void;\n onChangeFilter: (key: string, value: FilterValue) => void;\n}\n\nfunction hasOrder(filter: FilterDefinition): boolean {\n return typeof (filter as { order?: unknown }).order === 'number';\n}\n\nfunction FilterBar({\n filters,\n activeFilters,\n onAddFilter,\n onRemoveFilter,\n onChangeFilter,\n className,\n children,\n ref,\n ...props\n}: FilterBarProps) {\n const usedKeys = new Set(activeFilters.map(f => f.key));\n const remainingFilters = filters.filter(f => !usedKeys.has(f.key));\n const availableFilters = filters.some(hasOrder) ? remainingFilters : sortBy(remainingFilters, f => f.label);\n const shouldShowFilters = availableFilters.length > 0 || activeFilters.length > 0;\n\n // Tracks the most recently added filter so its chip can open its value\n // popup immediately, without waiting for a second click.\n const [pendingOpenKey, setPendingOpenKey] = React.useState<string | null>(null);\n\n function handleAddFilter(key: string) {\n onAddFilter(key);\n setPendingOpenKey(key);\n }\n\n // Ref for the FilterBar's own root node, used to anchor Combobox popups.\n // Helps rendering correctly the popup when rendered inside a modal Dialog/Sheet.\n const containerRef = React.useRef<HTMLDivElement>(null);\n\n return (\n <div\n data-slot=\"filter-bar\"\n data-visible-filters={shouldShowFilters}\n className={cn('flex flex-wrap items-center gap-2', className)}\n ref={composeRefs(containerRef, ref)}\n {...props}\n >\n {/* Add filter button */}\n {availableFilters.length > 0 && (\n <Combobox\n items={availableFilters}\n itemToStringValue={(item: FilterDefinition) => item.label}\n itemToStringLabel={(item: FilterDefinition) => item.label}\n onValueChange={(filter: FilterDefinition | null) => {\n if (filter) handleAddFilter(filter.key);\n }}\n >\n {/* role=\"combobox\" doesn't pick up child text as its accessible name the\n way a plain button would — an explicit aria-label is required, not\n just a nicety. */}\n <ComboboxTrigger aria-label=\"Add filter\" render={<Button variant=\"outline\" />}>\n <Icon name=\"plus\" data-icon=\"inline-start\" />\n Add filter\n </ComboboxTrigger>\n <ComboboxContent container={containerRef} align=\"start\" className=\"w-48\">\n <ComboboxInput showTrigger={false} placeholder=\"Search filters…\" />\n <ComboboxEmpty>No filters found.</ComboboxEmpty>\n <ComboboxList>\n {(filter: FilterDefinition) => (\n <ComboboxItem key={filter.key} value={filter}>\n {filter.label}\n </ComboboxItem>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n )}\n\n {/* Active filter chips */}\n {activeFilters.map(active => {\n const definition = filters.find(f => f.key === active.key);\n if (!definition) return null;\n return (\n <FilterChip\n key={active.key}\n definition={definition}\n value={active.value}\n autoOpen={active.key === pendingOpenKey}\n container={containerRef}\n onChangeValue={value => onChangeFilter(active.key, value)}\n onRemove={() => onRemoveFilter(active.key)}\n />\n );\n })}\n\n {/* Children (count, actions) — sit immediately after the last chip\n with the same `gap-2` spacing. With the parent's `flex-wrap`, a\n long row of chips will push the action onto the next line instead\n of stretching across an empty horizontal void. */}\n {children}\n </div>\n );\n}\n\n// ── FilterChip (dispatcher) ────────────────────────────\n\ninterface FilterChipProps {\n definition: FilterDefinition;\n value: FilterValue | undefined;\n /** Opens the chip's value popup as soon as it mounts — used right after the filter is added. */\n autoOpen?: boolean;\n /** Portals a searchable checkbox/radio chip's Combobox popup here instead of the default `document.body` — see `FilterBar`'s own `containerRef` for why. */\n container?: PortalContainer;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}\n\nfunction FilterChip({ definition, value, autoOpen, container, onChangeValue, onRemove }: FilterChipProps) {\n const type = definition.type ?? 'checkbox';\n\n switch (type) {\n case 'checkbox':\n return (\n <CheckboxFilterChip\n definition={definition as CheckboxFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n container={container}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n case 'radio':\n return (\n <RadioFilterChip\n definition={definition as RadioFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n container={container}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n case 'text':\n return (\n <TextFilterChip\n definition={definition as TextFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n case 'multiText':\n return (\n <MultiTextFilterChip\n definition={definition as MultiTextFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n case 'date':\n return (\n <DateFilterChip\n definition={definition as DateFilterDefinition}\n value={value}\n autoOpen={autoOpen}\n onChangeValue={onChangeValue}\n onRemove={onRemove}\n />\n );\n }\n}\n\n// ── Shared chip chrome ─────────────────────────────────\n\nfunction FilterChipRemoveButton({ label, onRemove }: { label: string; onRemove: () => void }) {\n return (\n <Button variant=\"outline\" size=\"icon\" aria-label={`Remove ${label} filter`} onClick={onRemove}>\n <Icon name=\"xmark\" />\n </Button>\n );\n}\n\n// ── Checkbox chip ──────────────────────────────────────\n\nfunction CheckboxFilterChip({\n definition,\n value,\n autoOpen,\n container,\n onChangeValue,\n onRemove,\n}: {\n definition: CheckboxFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n container?: PortalContainer;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, searchable = false } = definition;\n const options = sortBy(definition.options, o => o.label);\n const values = asCheckboxValue(value);\n const displayLabel =\n values.length === 0\n ? 'Any'\n : values.length === 1\n ? (options.find(o => o.value === values[0])?.label ?? values[0])\n : `${values.length} selected`;\n\n function toggle(optionValue: string) {\n const next = values.includes(optionValue) ? values.filter(v => v !== optionValue) : [...values, optionValue];\n onChangeValue(next);\n }\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n {searchable ? (\n <Combobox\n items={options}\n itemToStringValue={(item: FilterOption) => item.label}\n multiple\n defaultOpen={autoOpen}\n value={options.filter(o => values.includes(o.value))}\n onValueChange={(selected: FilterOption[]) => onChangeValue(selected.map(s => s.value))}\n >\n <ComboboxTrigger\n render={<Button variant=\"outline\" className=\"gap-1 font-normal [&>svg:last-child]:size-(--icon-sm)\" />}\n >\n <span className={values.length === 0 ? 'text-muted-foreground' : ''}>{displayLabel}</span>\n </ComboboxTrigger>\n <ComboboxContent\n container={container}\n className=\"min-w-64\"\n collisionAvoidance={{ side: 'none', align: 'shift' }}\n >\n <ComboboxInput showTrigger={false} placeholder={`Search ${label.toLowerCase()}…`} />\n {values.length > 0 && (\n <>\n <ComboboxSelectedChips>\n <ComboboxValue>\n {(selected: FilterOption[]) => (\n <>\n {selected.map(item => (\n <ComboboxChip key={item.value}>{item.label}</ComboboxChip>\n ))}\n </>\n )}\n </ComboboxValue>\n <ComboboxClearAll onClick={() => onChangeValue([])} />\n </ComboboxSelectedChips>\n <ComboboxSeparator />\n </>\n )}\n <ComboboxEmpty>No {label.toLowerCase()} found.</ComboboxEmpty>\n <ComboboxList>\n {(item: FilterOption) => (\n <ComboboxItem key={item.value} value={item}>\n {item.icon}\n {item.label}\n </ComboboxItem>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n ) : (\n <DropdownMenu defaultOpen={autoOpen}>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={values.length === 0 ? 'text-muted-foreground' : ''}>{displayLabel}</span>\n <Icon name=\"chevron-down\" size=\"sm\" className=\"text-muted-foreground\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {options.map(opt => (\n <DropdownMenuCheckboxItem\n key={opt.value}\n checked={values.includes(opt.value)}\n onCheckedChange={() => toggle(opt.value)}\n >\n {opt.icon}\n {opt.label}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\n// ── Radio chip ─────────────────────────────────────────\n\nfunction RadioFilterChip({\n definition,\n value,\n autoOpen,\n container,\n onChangeValue,\n onRemove,\n}: {\n definition: RadioFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n container?: PortalContainer;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, searchable = false } = definition;\n const options = sortBy(definition.options, o => o.label);\n const selectedValue = asRadioValue(value);\n const displayLabel = selectedValue ? (options.find(o => o.value === selectedValue)?.label ?? selectedValue) : 'Any';\n const selected = options.find(o => o.value === selectedValue) ?? null;\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n {searchable ? (\n <Combobox\n items={options}\n itemToStringValue={(item: FilterOption) => item.label}\n defaultOpen={autoOpen}\n value={selected}\n onValueChange={(next: FilterOption | null) => onChangeValue(next?.value ?? '')}\n >\n <ComboboxTrigger\n render={<Button variant=\"outline\" className=\"gap-1 font-normal [&>svg:last-child]:size-(--icon-sm)\" />}\n >\n <span className={selectedValue ? '' : 'text-muted-foreground'}>{displayLabel}</span>\n </ComboboxTrigger>\n <ComboboxContent\n container={container}\n className=\"min-w-64\"\n collisionAvoidance={{ side: 'none', align: 'shift' }}\n >\n <ComboboxInput showTrigger={false} placeholder={`Search ${label.toLowerCase()}…`} />\n <ComboboxEmpty>No {label.toLowerCase()} found.</ComboboxEmpty>\n <ComboboxList>\n {(item: FilterOption) => (\n <ComboboxItem key={item.value} value={item}>\n {item.icon}\n {item.label}\n </ComboboxItem>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n ) : (\n <DropdownMenu defaultOpen={autoOpen}>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={selectedValue ? '' : 'text-muted-foreground'}>{displayLabel}</span>\n <Icon name=\"chevron-down\" size=\"sm\" className=\"text-muted-foreground\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n <DropdownMenuRadioGroup value={selectedValue} onValueChange={onChangeValue}>\n {options.map(opt => (\n <DropdownMenuRadioItem key={opt.value} value={opt.value}>\n {opt.icon}\n {opt.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\n// ── Text chip ──────────────────────────────────────────\n\nfunction TextFilterChip({\n definition,\n value,\n autoOpen,\n onChangeValue,\n onRemove,\n}: {\n definition: TextFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, placeholder } = definition;\n const text = asTextValue(value);\n const [open, setOpen] = React.useState(!!autoOpen);\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={text ? '' : 'text-muted-foreground'}>{text || 'Any'}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\">\n <Input\n autoFocus\n value={text}\n placeholder={placeholder ?? 'Enter a value…'}\n onChange={e => onChangeValue(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter') setOpen(false);\n }}\n />\n </PopoverContent>\n </Popover>\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\n// ── MultiText chip ─────────────────────────────────────\n\nfunction MultiTextFilterChip({\n definition,\n value,\n autoOpen,\n onChangeValue,\n onRemove,\n}: {\n definition: MultiTextFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, placeholder } = definition;\n const tokens = asMultiTextValue(value);\n const [draft, setDraft] = React.useState('');\n const displayLabel = tokens.length === 0 ? 'Any' : tokens.length === 1 ? tokens[0] : `${tokens.length} values`;\n\n function addToken() {\n const token = draft.trim();\n if (!token || tokens.includes(token)) {\n setDraft('');\n return;\n }\n onChangeValue([...tokens, token]);\n setDraft('');\n }\n\n function removeToken(token: string) {\n onChangeValue(tokens.filter(t => t !== token));\n }\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n <Popover defaultOpen={autoOpen}>\n <PopoverTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={tokens.length === 0 ? 'text-muted-foreground' : ''}>{displayLabel}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\">\n <div className=\"flex flex-col gap-2\">\n {tokens.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {tokens.map(token => (\n <Badge key={token} variant=\"secondary\" className=\"gap-1\">\n {token}\n <button\n type=\"button\"\n aria-label={`Remove ${token}`}\n onClick={() => removeToken(token)}\n className=\"cursor-pointer\"\n >\n <Icon name=\"xmark\" size=\"xs\" />\n </button>\n </Badge>\n ))}\n </div>\n )}\n <Input\n autoFocus\n value={draft}\n placeholder={placeholder ?? 'Type a value, press Enter…'}\n onChange={e => setDraft(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter') {\n e.preventDefault();\n addToken();\n } else if (e.key === 'Backspace' && draft === '' && tokens.length > 0) {\n removeToken(tokens[tokens.length - 1]);\n }\n }}\n />\n </div>\n </PopoverContent>\n </Popover>\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\n// ── Date chip ──────────────────────────────────────────\n\nfunction DateFilterChip({\n definition,\n value,\n autoOpen,\n onChangeValue,\n onRemove,\n}: {\n definition: DateFilterDefinition;\n value: FilterValue | undefined;\n autoOpen?: boolean;\n onChangeValue: (value: FilterValue) => void;\n onRemove: () => void;\n}) {\n const { label, includeTime } = definition;\n const { startDate, endDate } = asDateValue(value);\n const showTimeInputs = includeTime ?? (hasExplicitTime(startDate) || hasExplicitTime(endDate));\n\n const displayLabel = !startDate\n ? 'Any'\n : !endDate || startDate.getTime() === endDate.getTime()\n ? formatDateLabel(startDate)\n : `${formatDateLabel(startDate)} – ${formatDateLabel(endDate)}`;\n\n function pickRange(range: DateRange | undefined) {\n if (!range?.from) {\n onChangeValue({ startDate: null, endDate: null });\n return;\n }\n onChangeValue({ startDate: range.from, endDate: range.to ?? range.from });\n }\n\n function pickStartTime(time: string) {\n if (!startDate) return;\n onChangeValue({ startDate: applyTimeToDate(startDate, time), endDate });\n }\n\n function pickEndTime(time: string) {\n if (!endDate) return;\n onChangeValue({ startDate, endDate: applyTimeToDate(endDate, time) });\n }\n\n return (\n <ButtonGroup>\n <ButtonGroupText>{label}</ButtonGroupText>\n <Popover defaultOpen={autoOpen}>\n <PopoverTrigger asChild>\n <Button variant=\"outline\" className=\"gap-1 font-normal\">\n <span className={startDate ? '' : 'text-muted-foreground'}>{displayLabel}</span>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-fit\">\n <div className=\"flex flex-col gap-2\">\n <Calendar\n mode=\"range\"\n selected={{ from: startDate ?? undefined, to: endDate ?? undefined }}\n onSelect={pickRange}\n />\n {showTimeInputs && (\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex w-full items-center gap-2\">\n <Label\n htmlFor={`${definition.key}-start-time`}\n className=\"text-body-sm text-muted-foreground w-10 shrink-0\"\n >\n Start\n </Label>\n <Input\n id={`${definition.key}-start-time`}\n type=\"time\"\n className=\"w-full\"\n value={formatTimeForInput(startDate)}\n onChange={e => pickStartTime(e.target.value)}\n disabled={!startDate}\n />\n </div>\n <div className=\"flex w-full items-center gap-2\">\n <Label\n htmlFor={`${definition.key}-end-time`}\n className=\"text-body-sm text-muted-foreground w-10 shrink-0\"\n >\n End\n </Label>\n <Input\n id={`${definition.key}-end-time`}\n type=\"time\"\n className=\"w-full\"\n value={formatTimeForInput(endDate)}\n onChange={e => pickEndTime(e.target.value)}\n disabled={!endDate}\n />\n </div>\n </div>\n )}\n </div>\n </PopoverContent>\n </Popover>\n <FilterChipRemoveButton label={label} onRemove={onRemove} />\n </ButtonGroup>\n );\n}\n\nexport {\n FilterBar,\n FilterChip,\n type FilterType,\n type FilterDefinition,\n type CheckboxFilterDefinition,\n type RadioFilterDefinition,\n type TextFilterDefinition,\n type MultiTextFilterDefinition,\n type DateFilterDefinition,\n type ActiveFilter,\n type FilterOption,\n type FilterValue,\n type CheckboxFilterValue,\n type RadioFilterValue,\n type TextFilterValue,\n type MultiTextFilterValue,\n type DateFilterValue,\n type FilterBarProps,\n};\n","import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n {\n text: [\n 'display-hero',\n 'display-kpi-sm',\n 'display-kpi',\n 'display-kpi-lg',\n 'display-feature',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'body-lg',\n 'body',\n 'body-sm',\n 'caption',\n 'eyebrow',\n ],\n },\n ],\n },\n },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport function sortBy<T>(items: T[], getKey: (item: T) => string): T[] {\n return [...items].sort((a, b) => getKey(a).localeCompare(getKey(b)));\n}\n"],"mappings":"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","names":["_a"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/floating-widget.tsx","../src/lib/utils.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport { Button } from '@upbound/monarch-core';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@upbound/monarch-core';\nimport { Icon } from '@upbound/monarch-core';\nimport { Kbd } from '@upbound/monarch-core';\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@upbound/monarch-core';\n\n// ---------------------------------------------------------------------------\n// Context — global open/collapsed state\n// ---------------------------------------------------------------------------\n//\n// `FloatingWidget` is a singleton — one instance per app, mounted at the root\n// layout. Any consumer (e.g. a button in the header) calls `useFloatingWidget()`\n// to toggle it. State is in-memory only for now; can be promoted to storage\n// later without changing the consumer API.\n\ntype FloatingWidgetState = {\n isOpen: boolean;\n isCollapsed: boolean;\n isExpanded: boolean;\n open: () => void;\n close: () => void;\n toggle: () => void;\n collapse: () => void;\n restore: () => void;\n expand: () => void;\n shrink: () => void;\n toggleExpand: () => void;\n};\n\nconst FloatingWidgetContext = React.createContext<FloatingWidgetState | null>(null);\n\nexport function FloatingWidgetProvider({ children }: { children: React.ReactNode }) {\n const [isOpen, setIsOpen] = React.useState(false);\n const [isCollapsed, setIsCollapsed] = React.useState(false);\n const [isExpanded, setIsExpanded] = React.useState(false);\n\n const value = React.useMemo<FloatingWidgetState>(\n () => ({\n isOpen,\n isCollapsed,\n isExpanded,\n open: () => {\n setIsOpen(true);\n setIsCollapsed(false);\n },\n close: () => {\n setIsOpen(false);\n setIsCollapsed(false);\n setIsExpanded(false);\n },\n toggle: () => {\n setIsOpen(prev => !prev);\n setIsCollapsed(false);\n },\n collapse: () => setIsCollapsed(true),\n restore: () => setIsCollapsed(false),\n expand: () => {\n setIsExpanded(true);\n setIsCollapsed(false);\n },\n shrink: () => setIsExpanded(false),\n toggleExpand: () => {\n setIsExpanded(prev => !prev);\n setIsCollapsed(false);\n },\n }),\n [isOpen, isCollapsed, isExpanded],\n );\n\n return <FloatingWidgetContext.Provider value={value}>{children}</FloatingWidgetContext.Provider>;\n}\n\nexport function useFloatingWidget() {\n const ctx = React.useContext(FloatingWidgetContext);\n if (!ctx) {\n throw new Error('useFloatingWidget must be used inside <FloatingWidgetProvider>.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// FloatingWidget — the surface\n// ---------------------------------------------------------------------------\n//\n// A persistent, modeless floating panel anchored to the bottom-right of the\n// viewport. Two visible states:\n// - **open**: full panel (~420×640 desktop, fullscreen on mobile)\n// - **collapsed**: toast-sized sliver showing just the title bar\n// When `isOpen` is false the widget is unmounted entirely.\n//\n// `FloatingWidget` doesn't prescribe the body content — chat, help, or\n// notification surfaces all drop in via `children`. Caller is responsible for\n// the body's internal layout (e.g. wrapping in `ChatThread`).\n\ninterface FloatingWidgetProps {\n /** Title shown in the header bar. */\n title?: string;\n /** Optional single-line subtitle rendered under the title. When present the\n * header expands vertically to make room; when absent the header stays on\n * its default single-line height. */\n subtitle?: string;\n /** Optional menu items rendered in the `···` kebab dropdown. */\n menuItems?: React.ReactNode;\n /** Body content — typically a `ChatThread` for AI chat, but any surface fits. */\n children?: React.ReactNode;\n className?: string;\n}\n\nexport function FloatingWidget({ title = 'Ask AI', subtitle, menuItems, children, className }: FloatingWidgetProps) {\n const { isOpen, isCollapsed, isExpanded, close, collapse, restore, toggleExpand } = useFloatingWidget();\n\n // User-dragged custom width. When set, overrides the compact/expanded\n // widths. Reset whenever isExpanded toggles (button-driven size change =\n // user intent shift back to preset sizes).\n const [dragWidth, setDragWidth] = React.useState<number | null>(null);\n const [isDragging, setIsDragging] = React.useState(false);\n React.useEffect(() => {\n setDragWidth(null);\n }, [isExpanded]);\n\n const MIN_WIDTH = 420;\n const widgetRef = React.useRef<HTMLDivElement | null>(null);\n\n // Drag-to-resize handler bound as a React prop on the handle. Uses window\n // listeners during the drag so the pointer stream continues when the cursor\n // leaves the thin handle strip; `preventDefault` on mousedown suppresses\n // text-selection. Bound as a React handler (not native addEventListener) so\n // that Radix's Slot merges it with `TooltipTrigger`'s own listeners cleanly.\n const handleResizeMouseDown = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n setIsDragging(true);\n const startX = e.clientX;\n const startWidth = widgetRef.current?.getBoundingClientRect().width ?? MIN_WIDTH;\n const maxWidth = window.innerWidth - 32;\n\n function onMove(moveEvent: MouseEvent) {\n moveEvent.preventDefault();\n const next = Math.max(MIN_WIDTH, Math.min(maxWidth, startWidth + (startX - moveEvent.clientX)));\n setDragWidth(next);\n }\n function onUp() {\n window.removeEventListener('mousemove', onMove);\n window.removeEventListener('mouseup', onUp);\n setIsDragging(false);\n }\n window.addEventListener('mousemove', onMove);\n window.addEventListener('mouseup', onUp);\n }, []);\n\n // Esc closes the widget when open. Matches the Esc kbd hint shown in the\n // close button's tooltip. Listener only attached while open.\n React.useEffect(() => {\n if (!isOpen) return;\n function handleKeydown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n close();\n }\n }\n document.addEventListener('keydown', handleKeydown);\n return () => document.removeEventListener('keydown', handleKeydown);\n }, [isOpen, close]);\n\n if (!isOpen) return null;\n\n // Custom drag width takes precedence over compact/expanded class-based\n // widths. Only applies to the ≥sm layout; mobile stays fullscreen via\n // inset-x-0. The min-width guard mirrors MIN_WIDTH so any browser resize\n // that shrinks viewport below the current drag width doesn't visually clip.\n const dragStyle: React.CSSProperties | undefined =\n dragWidth != null && !isCollapsed ? { width: `${dragWidth}px`, minWidth: `${MIN_WIDTH}px` } : undefined;\n\n return (\n <div\n ref={widgetRef}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-label={title}\n style={dragStyle}\n className={cn(\n // Position: bottom-right anchor with viewport padding; fullscreen on mobile\n 'bg-background fixed inset-x-0 top-0 bottom-0 z-50 flex flex-col shadow-xl',\n // Kill text selection app-wide (bubbles up via body handler? No — we\n // apply it locally to the widget; the drag handler stops propagation\n // via preventDefault which prevents the browser from initiating\n // selection while the pointer moves.\n isDragging && 'select-none',\n // `overflow-hidden` clips children to the rounded corners so the chat\n // input's edges don't square off the bottom of the widget.\n 'sm:inset-x-auto sm:top-auto sm:right-4 sm:bottom-4 sm:overflow-hidden sm:rounded-xl sm:border',\n // Size: open = full panel; collapsed = title-bar sliver; expanded = larger panel\n isCollapsed\n ? 'sm:h-auto sm:w-80'\n : isExpanded\n ? 'sm:h-[calc(100vh-2rem)] sm:w-[720px]'\n : 'sm:h-[640px] sm:max-h-[calc(100vh-2rem)] sm:w-[420px]',\n className,\n )}\n >\n {/* Resize handle — 12px hit area with a hover-revealed centered\n vertical bar. Bar sits at 48px tall (twice the Resizable primitive's\n `h-6`, per this surface's larger scale) and fades in on hover so\n the affordance stays visually calm until the user aims for it. */}\n {!isCollapsed && (\n <Tooltip>\n <TooltipTrigger asChild>\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize widget\"\n onMouseDown={handleResizeMouseDown}\n className=\"group absolute inset-y-0 left-0 z-20 hidden w-3 cursor-col-resize touch-none items-center justify-center bg-transparent select-none sm:flex\"\n >\n <div className=\"bg-border h-12 w-1 shrink-0 rounded-lg opacity-0 transition-opacity duration-150 group-hover:opacity-100\" />\n </div>\n </TooltipTrigger>\n <TooltipContent side=\"left\">Resize</TooltipContent>\n </Tooltip>\n )}\n\n {/* Header — always visible. Provides title + optional subtitle + window\n controls. When `subtitle` is passed, the title/subtitle stack occupies\n two lines; otherwise the header keeps its default single-line\n rhythm. */}\n <header className=\"flex min-h-12 shrink-0 items-center gap-1 border-b px-3 py-2\">\n <div className=\"flex min-w-0 flex-1 flex-col justify-center\">\n <span className=\"text-body truncate font-medium\">{title}</span>\n {subtitle && <span className=\"text-body-sm text-muted-foreground truncate\">{subtitle}</span>}\n </div>\n\n {/* Kebab — extra actions; only rendered when menuItems are provided */}\n {menuItems && !isCollapsed && (\n <Tooltip>\n <DropdownMenu>\n <TooltipTrigger asChild>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"More options\">\n <Icon name=\"ellipsis-vertical\" />\n </Button>\n </DropdownMenuTrigger>\n </TooltipTrigger>\n <DropdownMenuContent align=\"end\">{menuItems}</DropdownMenuContent>\n </DropdownMenu>\n <TooltipContent>More options</TooltipContent>\n </Tooltip>\n )}\n\n {/* Expand / Shrink — toggles the widget between compact (420×640) and\n expanded (720 × viewport height). Placed before Minimize so window-\n management controls (minimize → close) sit together on the trailing\n edge, matching macOS/Windows traffic-light ordering. */}\n {!isCollapsed && (\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={toggleExpand}\n aria-label={isExpanded ? 'Shrink widget' : 'Expand widget'}\n >\n <Icon name={isExpanded ? 'shrink' : 'arrow-up-right-and-arrow-down-left-from-center'} />\n </Button>\n </TooltipTrigger>\n <TooltipContent>{isExpanded ? 'Shrink widget' : 'Expand widget'}</TooltipContent>\n </Tooltip>\n )}\n\n {/* Minimize / Restore — paired next to Close for a conventional\n \"minimize | close\" window-controls grouping. */}\n <Tooltip>\n <TooltipTrigger asChild>\n {isCollapsed ? (\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={restore} aria-label=\"Restore widget\">\n <Icon name=\"window-maximize\" />\n </Button>\n ) : (\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={collapse} aria-label=\"Minimize widget\">\n <Icon name=\"minus\" />\n </Button>\n )}\n </TooltipTrigger>\n <TooltipContent>{isCollapsed ? 'Restore widget' : 'Minimize widget'}</TooltipContent>\n </Tooltip>\n\n {/* Close — Esc keyboard shortcut wired via the keydown handler above */}\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={close} aria-label=\"Close widget\">\n <Icon name=\"xmark\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent>\n Close widget\n <Kbd>Esc</Kbd>\n </TooltipContent>\n </Tooltip>\n </header>\n\n {/* Body — hidden when collapsed; consumer-provided content otherwise */}\n {!isCollapsed && <div className=\"flex min-h-0 flex-1 flex-col\">{children}</div>}\n </div>\n );\n}\n","import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n {\n text: [\n 'display-hero',\n 'display-kpi-sm',\n 'display-kpi',\n 'display-kpi-lg',\n 'display-feature',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'body-lg',\n 'body',\n 'body-sm',\n 'caption',\n 'eyebrow',\n ],\n },\n ],\n },\n },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;AAEA,YAAY,WAAW;;;ACFvB,SAAS,YAA6B;AACtC,SAAS,2BAA2B;AAEpC,IAAM,UAAU,oBAAoB;AAAA,EAClC,QAAQ;AAAA,IACN,aAAa;AAAA,MACX,aAAa;AAAA,QACX;AAAA,UACE,MAAM;AAAA,YACJ;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAEM,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;AD3BA,SAAS,cAAc;AACvB,SAAS,cAAc,qBAAqB,2BAA2B;AACvE,SAAS,YAAY;AACrB,SAAS,WAAW;AACpB,SAAS,SAAS,gBAAgB,sBAAsB;AAiE/C,cAsID,YAtIC;AAxCT,IAAM,wBAA8B,oBAA0C,IAAI;AAE3E,SAAS,uBAAuB,EAAE,SAAS,GAAkC;AAClF,QAAM,CAAC,QAAQ,SAAS,IAAU,eAAS,KAAK;AAChD,QAAM,CAAC,aAAa,cAAc,IAAU,eAAS,KAAK;AAC1D,QAAM,CAAC,YAAY,aAAa,IAAU,eAAS,KAAK;AAExD,QAAM,QAAc;AAAA,IAClB,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM,MAAM;AACV,kBAAU,IAAI;AACd,uBAAe,KAAK;AAAA,MACtB;AAAA,MACA,OAAO,MAAM;AACX,kBAAU,KAAK;AACf,uBAAe,KAAK;AACpB,sBAAc,KAAK;AAAA,MACrB;AAAA,MACA,QAAQ,MAAM;AACZ,kBAAU,UAAQ,CAAC,IAAI;AACvB,uBAAe,KAAK;AAAA,MACtB;AAAA,MACA,UAAU,MAAM,eAAe,IAAI;AAAA,MACnC,SAAS,MAAM,eAAe,KAAK;AAAA,MACnC,QAAQ,MAAM;AACZ,sBAAc,IAAI;AAClB,uBAAe,KAAK;AAAA,MACtB;AAAA,MACA,QAAQ,MAAM,cAAc,KAAK;AAAA,MACjC,cAAc,MAAM;AAClB,sBAAc,UAAQ,CAAC,IAAI;AAC3B,uBAAe,KAAK;AAAA,MACtB;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,aAAa,UAAU;AAAA,EAClC;AAEA,SAAO,oBAAC,sBAAsB,UAAtB,EAA+B,OAAe,UAAS;AACjE;AAEO,SAAS,oBAAoB;AAClC,QAAM,MAAY,iBAAW,qBAAqB;AAClD,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AACA,SAAO;AACT;AA8BO,SAAS,eAAe,EAAE,QAAQ,UAAU,UAAU,WAAW,UAAU,UAAU,GAAwB;AAClH,QAAM,EAAE,QAAQ,aAAa,YAAY,OAAO,UAAU,SAAS,aAAa,IAAI,kBAAkB;AAKtG,QAAM,CAAC,WAAW,YAAY,IAAU,eAAwB,IAAI;AACpE,QAAM,CAAC,YAAY,aAAa,IAAU,eAAS,KAAK;AACxD,EAAM,gBAAU,MAAM;AACpB,iBAAa,IAAI;AAAA,EACnB,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,YAAY;AAClB,QAAM,YAAkB,aAA8B,IAAI;AAO1D,QAAM,wBAA8B,kBAAY,CAAC,MAAwC;AArI3F;AAsII,MAAE,eAAe;AACjB,kBAAc,IAAI;AAClB,UAAM,SAAS,EAAE;AACjB,UAAM,cAAa,qBAAU,YAAV,mBAAmB,wBAAwB,UAA3C,YAAoD;AACvE,UAAM,WAAW,OAAO,aAAa;AAErC,aAAS,OAAO,WAAuB;AACrC,gBAAU,eAAe;AACzB,YAAM,OAAO,KAAK,IAAI,WAAW,KAAK,IAAI,UAAU,cAAc,SAAS,UAAU,QAAQ,CAAC;AAC9F,mBAAa,IAAI;AAAA,IACnB;AACA,aAAS,OAAO;AACd,aAAO,oBAAoB,aAAa,MAAM;AAC9C,aAAO,oBAAoB,WAAW,IAAI;AAC1C,oBAAc,KAAK;AAAA,IACrB;AACA,WAAO,iBAAiB,aAAa,MAAM;AAC3C,WAAO,iBAAiB,WAAW,IAAI;AAAA,EACzC,GAAG,CAAC,CAAC;AAIL,EAAM,gBAAU,MAAM;AACpB,QAAI,CAAC,OAAQ;AACb,aAAS,cAAc,GAAkB;AACvC,UAAI,EAAE,QAAQ,UAAU;AACtB,cAAM;AAAA,MACR;AAAA,IACF;AACA,aAAS,iBAAiB,WAAW,aAAa;AAClD,WAAO,MAAM,SAAS,oBAAoB,WAAW,aAAa;AAAA,EACpE,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,MAAI,CAAC,OAAQ,QAAO;AAMpB,QAAM,YACJ,aAAa,QAAQ,CAAC,cAAc,EAAE,OAAO,GAAG,SAAS,MAAM,UAAU,GAAG,SAAS,KAAK,IAAI;AAEhG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,MAAK;AAAA,MACL,cAAW;AAAA,MACX,cAAY;AAAA,MACZ,OAAO;AAAA,MACP,WAAW;AAAA;AAAA,QAET;AAAA;AAAA;AAAA;AAAA;AAAA,QAKA,cAAc;AAAA;AAAA;AAAA,QAGd;AAAA;AAAA,QAEA,cACI,sBACA,aACE,yCACA;AAAA,QACN;AAAA,MACF;AAAA,MAMC;AAAA,SAAC,eACA,qBAAC,WACC;AAAA,8BAAC,kBAAe,SAAO,MACrB;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,oBAAiB;AAAA,cACjB,cAAW;AAAA,cACX,aAAa;AAAA,cACb,WAAU;AAAA,cAEV,8BAAC,SAAI,WAAU,4GAA2G;AAAA;AAAA,UAC5H,GACF;AAAA,UACA,oBAAC,kBAAe,MAAK,QAAO,oBAAM;AAAA,WACpC;AAAA,QAOF,qBAAC,YAAO,WAAU,gEAChB;AAAA,+BAAC,SAAI,WAAU,+CACb;AAAA,gCAAC,UAAK,WAAU,kCAAkC,iBAAM;AAAA,YACvD,YAAY,oBAAC,UAAK,WAAU,+CAA+C,oBAAS;AAAA,aACvF;AAAA,UAGC,aAAa,CAAC,eACb,qBAAC,WACC;AAAA,iCAAC,gBACC;AAAA,kCAAC,kBAAe,SAAO,MACrB,8BAAC,uBAAoB,SAAO,MAC1B,8BAAC,UAAO,SAAQ,SAAQ,MAAK,WAAU,cAAW,gBAChD,8BAAC,QAAK,MAAK,qBAAoB,GACjC,GACF,GACF;AAAA,cACA,oBAAC,uBAAoB,OAAM,OAAO,qBAAU;AAAA,eAC9C;AAAA,YACA,oBAAC,kBAAe,0BAAY;AAAA,aAC9B;AAAA,UAOD,CAAC,eACA,qBAAC,WACC;AAAA,gCAAC,kBAAe,SAAO,MACrB;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,SAAS;AAAA,gBACT,cAAY,aAAa,kBAAkB;AAAA,gBAE3C,8BAAC,QAAK,MAAM,aAAa,WAAW,kDAAkD;AAAA;AAAA,YACxF,GACF;AAAA,YACA,oBAAC,kBAAgB,uBAAa,kBAAkB,iBAAgB;AAAA,aAClE;AAAA,UAKF,qBAAC,WACC;AAAA,gCAAC,kBAAe,SAAO,MACpB,wBACC,oBAAC,UAAO,SAAQ,SAAQ,MAAK,WAAU,SAAS,SAAS,cAAW,kBAClE,8BAAC,QAAK,MAAK,mBAAkB,GAC/B,IAEA,oBAAC,UAAO,SAAQ,SAAQ,MAAK,WAAU,SAAS,UAAU,cAAW,mBACnE,8BAAC,QAAK,MAAK,SAAQ,GACrB,GAEJ;AAAA,YACA,oBAAC,kBAAgB,wBAAc,mBAAmB,mBAAkB;AAAA,aACtE;AAAA,UAGA,qBAAC,WACC;AAAA,gCAAC,kBAAe,SAAO,MACrB,8BAAC,UAAO,SAAQ,SAAQ,MAAK,WAAU,SAAS,OAAO,cAAW,gBAChE,8BAAC,QAAK,MAAK,SAAQ,GACrB,GACF;AAAA,YACA,qBAAC,kBAAe;AAAA;AAAA,cAEd,oBAAC,OAAI,iBAAG;AAAA,eACV;AAAA,aACF;AAAA,WACF;AAAA,QAGC,CAAC,eAAe,oBAAC,SAAI,WAAU,gCAAgC,UAAS;AAAA;AAAA;AAAA,EAC3E;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/floating-widget.tsx","../src/lib/utils.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport { Button } from '@upbound/monarch-core';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@upbound/monarch-core';\nimport { Icon } from '@upbound/monarch-core';\nimport { Kbd } from '@upbound/monarch-core';\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@upbound/monarch-core';\n\n// ---------------------------------------------------------------------------\n// Context — global open/collapsed state\n// ---------------------------------------------------------------------------\n//\n// `FloatingWidget` is a singleton — one instance per app, mounted at the root\n// layout. Any consumer (e.g. a button in the header) calls `useFloatingWidget()`\n// to toggle it. State is in-memory only for now; can be promoted to storage\n// later without changing the consumer API.\n\ntype FloatingWidgetState = {\n isOpen: boolean;\n isCollapsed: boolean;\n isExpanded: boolean;\n open: () => void;\n close: () => void;\n toggle: () => void;\n collapse: () => void;\n restore: () => void;\n expand: () => void;\n shrink: () => void;\n toggleExpand: () => void;\n};\n\nconst FloatingWidgetContext = React.createContext<FloatingWidgetState | null>(null);\n\nexport function FloatingWidgetProvider({ children }: { children: React.ReactNode }) {\n const [isOpen, setIsOpen] = React.useState(false);\n const [isCollapsed, setIsCollapsed] = React.useState(false);\n const [isExpanded, setIsExpanded] = React.useState(false);\n\n const value = React.useMemo<FloatingWidgetState>(\n () => ({\n isOpen,\n isCollapsed,\n isExpanded,\n open: () => {\n setIsOpen(true);\n setIsCollapsed(false);\n },\n close: () => {\n setIsOpen(false);\n setIsCollapsed(false);\n setIsExpanded(false);\n },\n toggle: () => {\n setIsOpen(prev => !prev);\n setIsCollapsed(false);\n },\n collapse: () => setIsCollapsed(true),\n restore: () => setIsCollapsed(false),\n expand: () => {\n setIsExpanded(true);\n setIsCollapsed(false);\n },\n shrink: () => setIsExpanded(false),\n toggleExpand: () => {\n setIsExpanded(prev => !prev);\n setIsCollapsed(false);\n },\n }),\n [isOpen, isCollapsed, isExpanded],\n );\n\n return <FloatingWidgetContext.Provider value={value}>{children}</FloatingWidgetContext.Provider>;\n}\n\nexport function useFloatingWidget() {\n const ctx = React.useContext(FloatingWidgetContext);\n if (!ctx) {\n throw new Error('useFloatingWidget must be used inside <FloatingWidgetProvider>.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// FloatingWidget — the surface\n// ---------------------------------------------------------------------------\n//\n// A persistent, modeless floating panel anchored to the bottom-right of the\n// viewport. Two visible states:\n// - **open**: full panel (~420×640 desktop, fullscreen on mobile)\n// - **collapsed**: toast-sized sliver showing just the title bar\n// When `isOpen` is false the widget is unmounted entirely.\n//\n// `FloatingWidget` doesn't prescribe the body content — chat, help, or\n// notification surfaces all drop in via `children`. Caller is responsible for\n// the body's internal layout (e.g. wrapping in `ChatThread`).\n\ninterface FloatingWidgetProps {\n /** Title shown in the header bar. */\n title?: string;\n /** Optional single-line subtitle rendered under the title. When present the\n * header expands vertically to make room; when absent the header stays on\n * its default single-line height. */\n subtitle?: string;\n /** Optional menu items rendered in the `···` kebab dropdown. */\n menuItems?: React.ReactNode;\n /** Body content — typically a `ChatThread` for AI chat, but any surface fits. */\n children?: React.ReactNode;\n className?: string;\n}\n\nexport function FloatingWidget({ title = 'Ask AI', subtitle, menuItems, children, className }: FloatingWidgetProps) {\n const { isOpen, isCollapsed, isExpanded, close, collapse, restore, toggleExpand } = useFloatingWidget();\n\n // User-dragged custom width. When set, overrides the compact/expanded\n // widths. Reset whenever isExpanded toggles (button-driven size change =\n // user intent shift back to preset sizes).\n const [dragWidth, setDragWidth] = React.useState<number | null>(null);\n const [isDragging, setIsDragging] = React.useState(false);\n React.useEffect(() => {\n setDragWidth(null);\n }, [isExpanded]);\n\n const MIN_WIDTH = 420;\n const widgetRef = React.useRef<HTMLDivElement | null>(null);\n\n // Drag-to-resize handler bound as a React prop on the handle. Uses window\n // listeners during the drag so the pointer stream continues when the cursor\n // leaves the thin handle strip; `preventDefault` on mousedown suppresses\n // text-selection. Bound as a React handler (not native addEventListener) so\n // that Radix's Slot merges it with `TooltipTrigger`'s own listeners cleanly.\n const handleResizeMouseDown = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n setIsDragging(true);\n const startX = e.clientX;\n const startWidth = widgetRef.current?.getBoundingClientRect().width ?? MIN_WIDTH;\n const maxWidth = window.innerWidth - 32;\n\n function onMove(moveEvent: MouseEvent) {\n moveEvent.preventDefault();\n const next = Math.max(MIN_WIDTH, Math.min(maxWidth, startWidth + (startX - moveEvent.clientX)));\n setDragWidth(next);\n }\n function onUp() {\n window.removeEventListener('mousemove', onMove);\n window.removeEventListener('mouseup', onUp);\n setIsDragging(false);\n }\n window.addEventListener('mousemove', onMove);\n window.addEventListener('mouseup', onUp);\n }, []);\n\n // Esc closes the widget when open. Matches the Esc kbd hint shown in the\n // close button's tooltip. Listener only attached while open.\n React.useEffect(() => {\n if (!isOpen) return;\n function handleKeydown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n close();\n }\n }\n document.addEventListener('keydown', handleKeydown);\n return () => document.removeEventListener('keydown', handleKeydown);\n }, [isOpen, close]);\n\n if (!isOpen) return null;\n\n // Custom drag width takes precedence over compact/expanded class-based\n // widths. Only applies to the ≥sm layout; mobile stays fullscreen via\n // inset-x-0. The min-width guard mirrors MIN_WIDTH so any browser resize\n // that shrinks viewport below the current drag width doesn't visually clip.\n const dragStyle: React.CSSProperties | undefined =\n dragWidth != null && !isCollapsed ? { width: `${dragWidth}px`, minWidth: `${MIN_WIDTH}px` } : undefined;\n\n return (\n <div\n ref={widgetRef}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-label={title}\n style={dragStyle}\n className={cn(\n // Position: bottom-right anchor with viewport padding; fullscreen on mobile\n 'bg-background fixed inset-x-0 top-0 bottom-0 z-50 flex flex-col shadow-xl',\n // Kill text selection app-wide (bubbles up via body handler? No — we\n // apply it locally to the widget; the drag handler stops propagation\n // via preventDefault which prevents the browser from initiating\n // selection while the pointer moves.\n isDragging && 'select-none',\n // `overflow-hidden` clips children to the rounded corners so the chat\n // input's edges don't square off the bottom of the widget.\n 'sm:inset-x-auto sm:top-auto sm:right-4 sm:bottom-4 sm:overflow-hidden sm:rounded-xl sm:border',\n // Size: open = full panel; collapsed = title-bar sliver; expanded = larger panel\n isCollapsed\n ? 'sm:h-auto sm:w-80'\n : isExpanded\n ? 'sm:h-[calc(100vh-2rem)] sm:w-[720px]'\n : 'sm:h-[640px] sm:max-h-[calc(100vh-2rem)] sm:w-[420px]',\n className,\n )}\n >\n {/* Resize handle — 12px hit area with a hover-revealed centered\n vertical bar. Bar sits at 48px tall (twice the Resizable primitive's\n `h-6`, per this surface's larger scale) and fades in on hover so\n the affordance stays visually calm until the user aims for it. */}\n {!isCollapsed && (\n <Tooltip>\n <TooltipTrigger asChild>\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize widget\"\n onMouseDown={handleResizeMouseDown}\n className=\"group absolute inset-y-0 left-0 z-20 hidden w-3 cursor-col-resize touch-none items-center justify-center bg-transparent select-none sm:flex\"\n >\n <div className=\"bg-border h-12 w-1 shrink-0 rounded-lg opacity-0 transition-opacity duration-150 group-hover:opacity-100\" />\n </div>\n </TooltipTrigger>\n <TooltipContent side=\"left\">Resize</TooltipContent>\n </Tooltip>\n )}\n\n {/* Header — always visible. Provides title + optional subtitle + window\n controls. When `subtitle` is passed, the title/subtitle stack occupies\n two lines; otherwise the header keeps its default single-line\n rhythm. */}\n <header className=\"flex min-h-12 shrink-0 items-center gap-1 border-b px-3 py-2\">\n <div className=\"flex min-w-0 flex-1 flex-col justify-center\">\n <span className=\"text-body truncate font-medium\">{title}</span>\n {subtitle && <span className=\"text-body-sm text-muted-foreground truncate\">{subtitle}</span>}\n </div>\n\n {/* Kebab — extra actions; only rendered when menuItems are provided */}\n {menuItems && !isCollapsed && (\n <Tooltip>\n <DropdownMenu>\n <TooltipTrigger asChild>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"More options\">\n <Icon name=\"ellipsis-vertical\" />\n </Button>\n </DropdownMenuTrigger>\n </TooltipTrigger>\n <DropdownMenuContent align=\"end\">{menuItems}</DropdownMenuContent>\n </DropdownMenu>\n <TooltipContent>More options</TooltipContent>\n </Tooltip>\n )}\n\n {/* Expand / Shrink — toggles the widget between compact (420×640) and\n expanded (720 × viewport height). Placed before Minimize so window-\n management controls (minimize → close) sit together on the trailing\n edge, matching macOS/Windows traffic-light ordering. */}\n {!isCollapsed && (\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={toggleExpand}\n aria-label={isExpanded ? 'Shrink widget' : 'Expand widget'}\n >\n <Icon name={isExpanded ? 'shrink' : 'arrow-up-right-and-arrow-down-left-from-center'} />\n </Button>\n </TooltipTrigger>\n <TooltipContent>{isExpanded ? 'Shrink widget' : 'Expand widget'}</TooltipContent>\n </Tooltip>\n )}\n\n {/* Minimize / Restore — paired next to Close for a conventional\n \"minimize | close\" window-controls grouping. */}\n <Tooltip>\n <TooltipTrigger asChild>\n {isCollapsed ? (\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={restore} aria-label=\"Restore widget\">\n <Icon name=\"window-maximize\" />\n </Button>\n ) : (\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={collapse} aria-label=\"Minimize widget\">\n <Icon name=\"minus\" />\n </Button>\n )}\n </TooltipTrigger>\n <TooltipContent>{isCollapsed ? 'Restore widget' : 'Minimize widget'}</TooltipContent>\n </Tooltip>\n\n {/* Close — Esc keyboard shortcut wired via the keydown handler above */}\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={close} aria-label=\"Close widget\">\n <Icon name=\"xmark\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent>\n Close widget\n <Kbd>Esc</Kbd>\n </TooltipContent>\n </Tooltip>\n </header>\n\n {/* Body — hidden when collapsed; consumer-provided content otherwise */}\n {!isCollapsed && <div className=\"flex min-h-0 flex-1 flex-col\">{children}</div>}\n </div>\n );\n}\n","import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n {\n text: [\n 'display-hero',\n 'display-kpi-sm',\n 'display-kpi',\n 'display-kpi-lg',\n 'display-feature',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'body-lg',\n 'body',\n 'body-sm',\n 'caption',\n 'eyebrow',\n ],\n },\n ],\n },\n },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport function sortBy<T>(items: T[], getKey: (item: T) => string): T[] {\n return [...items].sort((a, b) => getKey(a).localeCompare(getKey(b)));\n}\n"],"mappings":"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","names":[]}
package/dist/graph.css ADDED
@@ -0,0 +1,73 @@
1
+ /*
2
+ * Required once, only if you use `Graph` / `GraphView`:
3
+ *
4
+ * @import '@upbound/monarch-blocks/graph.css';
5
+ *
6
+ * next to `@upbound/monarch-core/theme.css` in your Tailwind entry CSS. This
7
+ * package ships no other CSS
8
+ *
9
+ */
10
+
11
+ @import "reactflow/dist/base.css";
12
+
13
+ .react-flow__node-default {
14
+ border: none !important;
15
+ background: transparent !important;
16
+ padding: 0 !important;
17
+ margin: 0 !important;
18
+ box-shadow: none !important;
19
+ }
20
+
21
+ .react-flow__node {
22
+ border: none !important;
23
+ background: transparent !important;
24
+ box-shadow: none !important;
25
+ }
26
+
27
+ .react-flow__controls {
28
+ overflow: hidden;
29
+ border: 1px solid var(--border);
30
+ border-radius: var(--radius);
31
+ box-shadow: none;
32
+ }
33
+
34
+ .react-flow__controls-button {
35
+ background: var(--card);
36
+ border-bottom: 1px solid var(--border);
37
+ color: var(--foreground);
38
+ }
39
+
40
+ .react-flow__controls-button svg {
41
+ fill: var(--foreground);
42
+ }
43
+
44
+ .react-flow__controls-button:hover {
45
+ background: var(--accent);
46
+ }
47
+
48
+ .react-flow__controls-button:disabled svg {
49
+ fill: var(--muted-foreground);
50
+ }
51
+
52
+ .react-flow__minimap {
53
+ background-color: var(--card);
54
+ border: 1px solid var(--border);
55
+ border-radius: var(--radius);
56
+ overflow: hidden;
57
+ }
58
+
59
+ .react-flow__minimap-node {
60
+ fill: var(--muted-foreground);
61
+ }
62
+
63
+ .react-flow__minimap-mask {
64
+ fill: var(--muted);
65
+ }
66
+
67
+ .react-flow__attribution {
68
+ background: var(--card);
69
+ }
70
+
71
+ .react-flow__attribution a {
72
+ color: var(--muted-foreground);
73
+ }
@@ -0,0 +1,182 @@
1
+ import { Node, NodeProps, Edge, EdgeProps, NodeTypes, EdgeTypes } from 'reactflow';
2
+ export { Edge, Node } from 'reactflow';
3
+ import * as React from 'react';
4
+ import { ColumnDef, ColumnFiltersState } from '@tanstack/react-table';
5
+ import { D as DataTableEmptyConfig, T as TableDataState } from './empty-view-DO36LNvk.js';
6
+ import '@upbound/monarch-core';
7
+
8
+ type GraphBadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link' | 'healthy' | 'unhealthy' | 'warning' | 'stale' | 'info' | 'unknown';
9
+ type BadgeWithTooltip = {
10
+ variant?: GraphBadgeVariant;
11
+ tooltipContent?: string;
12
+ children: React.ReactNode;
13
+ };
14
+ interface NodeData<T extends object = object> {
15
+ rawData: T;
16
+ title: string;
17
+ subtitle: string;
18
+ name: string;
19
+ typeDescriptors: {
20
+ className?: string;
21
+ default: string;
22
+ compact?: string;
23
+ };
24
+ badges: {
25
+ default: BadgeWithTooltip[];
26
+ compact?: BadgeWithTooltip[];
27
+ };
28
+ compact?: boolean;
29
+ state?: 'default' | 'progressing' | 'paused';
30
+ onClick?: (nodeId: string, data: NodeData) => void;
31
+ expanded?: boolean;
32
+ onExpandToggle?: (nodeId: string) => void;
33
+ isExpandable?: boolean;
34
+ isHighlighted?: boolean;
35
+ }
36
+ type NodeLoading = Node & {
37
+ type: 'loading';
38
+ data?: {};
39
+ };
40
+ type NodeDefault<T extends object = object> = Node<NodeData<T>> & {
41
+ type?: 'default';
42
+ };
43
+ declare function isDefaultNode(node: NodeLoading | NodeDefault): node is NodeDefault;
44
+ type GraphNodeProps<T extends NodeData | {}> = NodeProps<T>;
45
+ declare function GraphNode<T extends NodeData = NodeData>({ id, data, ...props }: GraphNodeProps<T>): React.JSX.Element;
46
+ declare function GraphNodeSkeleton(_props: NodeProps<{}>): React.JSX.Element;
47
+
48
+ interface GraphActionsProviderProps {
49
+ initialNodes?: (NodeLoading | NodeDefault)[];
50
+ initialEdges?: Edge[];
51
+ children: React.ReactNode;
52
+ }
53
+ interface GraphActionsProviderRef {
54
+ addNodes: (newNodes: (NodeLoading | NodeDefault)[]) => void;
55
+ addEdges: (newEdges: Edge[]) => void;
56
+ setNodes: (nodes: (NodeLoading | NodeDefault)[]) => void;
57
+ setEdges: (edges: Edge[]) => void;
58
+ toggleNodeExpansion: (nodeId: string) => void;
59
+ updateNode: (nodeId: string, node: NodeLoading | NodeDefault) => void;
60
+ reset: () => void;
61
+ }
62
+ declare const GraphActionsProvider: React.ForwardRefExoticComponent<GraphActionsProviderProps & React.RefAttributes<GraphActionsProviderRef>>;
63
+
64
+ declare const GraphEdge: React.FC<EdgeProps & {
65
+ stopOpacity?: number;
66
+ }>;
67
+
68
+ interface GraphOptions<T extends object> {
69
+ data: T[];
70
+ nodeTransform: (item: T, data: T[]) => NodeLoading | NodeDefault | (NodeLoading | NodeDefault)[];
71
+ edgeTransform: (item: T) => Edge[] | undefined;
72
+ nodeTypes?: NodeTypes;
73
+ edgeTypes?: EdgeTypes;
74
+ }
75
+ type GraphDataState = TableDataState;
76
+ interface GraphEmptyView {
77
+ empty?: DataTableEmptyConfig;
78
+ error?: DataTableEmptyConfig;
79
+ filter?: DataTableEmptyConfig;
80
+ }
81
+ interface GraphProps<T extends object> {
82
+ id: string;
83
+ viewportFocusNodeId?: string;
84
+ config: GraphOptions<T>;
85
+ columnDefinitions?: ColumnDef<T, unknown>[];
86
+ title?: React.ReactNode;
87
+ loading?: boolean;
88
+ showError?: boolean;
89
+ className?: string;
90
+ emptyView?: GraphEmptyView;
91
+ ActionsComponent?: React.ComponentType<{
92
+ state: GraphDataState;
93
+ }>;
94
+ disableSearch?: boolean;
95
+ disableFilters?: boolean;
96
+ searchTerm?: string;
97
+ onSearchTermChange?: React.Dispatch<React.SetStateAction<string>>;
98
+ filters?: ColumnFiltersState;
99
+ onFiltersChange?: React.Dispatch<React.SetStateAction<ColumnFiltersState>>;
100
+ }
101
+ declare const defaultGraphErrorConfig: DataTableEmptyConfig;
102
+ declare const defaultGraphEmptyConfig: DataTableEmptyConfig;
103
+ declare const defaultGraphNoFilterFoundConfig: DataTableEmptyConfig;
104
+ declare function Graph<T extends object>({ id, viewportFocusNodeId, config, columnDefinitions, title, emptyView, showError, className, loading, ActionsComponent: Actions, disableSearch, disableFilters, searchTerm: parentGlobalFilter, onSearchTermChange: parentSetGlobalFilter, filters: parentColumnFilters, onFiltersChange: parentSetColumnFilters, children, }: React.PropsWithChildren<GraphProps<T>>): React.JSX.Element;
105
+
106
+ declare const GraphView: React.ForwardRefExoticComponent<{
107
+ viewportFocusNodeId?: string;
108
+ defaultNodes?: (NodeLoading | NodeDefault)[];
109
+ defaultEdges?: Edge[];
110
+ nodeTypes?: NodeTypes;
111
+ edgeTypes?: EdgeTypes;
112
+ children?: React.ReactNode;
113
+ } & {
114
+ children?: React.ReactNode | undefined;
115
+ } & React.RefAttributes<GraphActionsProviderRef>>;
116
+
117
+ /**
118
+ * Auto Layout Algorithm for Graph Positioning
119
+ *
120
+ * This hook implements a tree-based layout algorithm that positions nodes in a hierarchical graph.
121
+ * The algorithm ensures proper spacing and positioning by finding the actual lowest positioned nodes.
122
+ *
123
+ * Flow Overview:
124
+ * 1. Filter visible nodes (exclude hidden nodes from layout calculations)
125
+ * 2. Build tree structure from nodes and edges using getTrees()
126
+ * 3. Position nodes recursively with positionNodes():
127
+ * - Calculate X position based on node depth in hierarchy
128
+ * - Calculate Y position based on actual lowest positioned node in previous sibling's subtree
129
+ * - Use getLowestPositionYInSubtree() to find the real bottom of each subtree
130
+ * 4. Flatten positioned tree back to array of nodes
131
+ * 5. Return only visible nodes with their calculated positions
132
+ *
133
+ * Key Features:
134
+ * - Sibling nodes are positioned below the actual lowest node in previous sibling's entire subtree
135
+ * - Uses Math.max to find the deepest positioned node across all child subtrees
136
+ * - Proper horizontal spacing based on hierarchy depth
137
+ * - Vertical spacing prevents node overlapping by using real node positions
138
+ * - Hidden nodes are completely excluded from ReactFlow
139
+ * - Loading nodes are properly displayed when parent is expanded
140
+ *
141
+ */
142
+
143
+ interface AutoLayoutReturn {
144
+ nodes: (NodeLoading | NodeDefault)[];
145
+ edges: Edge[];
146
+ nodeMap: Map<string, NodeLoading | NodeDefault>;
147
+ }
148
+ declare const useAutoLayout: (nodes: (NodeLoading | NodeDefault)[], edges: Edge[], { viewportFocusNodeId }: {
149
+ viewportFocusNodeId?: string;
150
+ }) => AutoLayoutReturn;
151
+
152
+ interface UseNodeVisibilityReturn {
153
+ nodes: (NodeLoading | NodeDefault)[];
154
+ edges: Edge[];
155
+ }
156
+ declare const useNodeVisibility: (nodes: (NodeLoading | NodeDefault)[], edges: Edge[]) => UseNodeVisibilityReturn;
157
+
158
+ interface GraphActions {
159
+ nodes: (NodeLoading | NodeDefault)[];
160
+ edges: Edge[];
161
+ addNodes: (newNodes: (NodeLoading | NodeDefault)[]) => void;
162
+ addEdges: (newEdges: Edge[]) => void;
163
+ removeNodes: (nodeIds: string[]) => void;
164
+ removeEdges: (edgeIds: string[]) => void;
165
+ setNodes: (nodes: (NodeLoading | NodeDefault)[]) => void;
166
+ setEdges: (edges: Edge[]) => void;
167
+ updateNode: (nodeId: string, node: NodeLoading | NodeDefault) => void;
168
+ toggleNodeExpansion: (nodeId: string) => void;
169
+ resetGraph: () => void;
170
+ }
171
+ declare const useGraphActions: (initialNodes?: (NodeLoading | NodeDefault)[], initialEdges?: Edge[]) => GraphActions;
172
+
173
+ declare const useGraphActionsContext: () => GraphActions;
174
+
175
+ declare const useGetViewportNodeIds: (debounceDelay?: number) => string[];
176
+
177
+ declare const getGraphDescendants: (node: Node, nodes: Node[], edges: Edge[]) => {
178
+ nodes: Node[];
179
+ edges: Edge[];
180
+ };
181
+
182
+ export { type AutoLayoutReturn, Graph, type GraphActions, GraphActionsProvider, type GraphActionsProviderRef, type GraphBadgeVariant, type GraphDataState, GraphEdge, type GraphEmptyView, GraphNode, type GraphNodeProps, GraphNodeSkeleton, type GraphOptions, type GraphProps, GraphView, type NodeData, type NodeDefault, type NodeLoading, type UseNodeVisibilityReturn, defaultGraphEmptyConfig, defaultGraphErrorConfig, defaultGraphNoFilterFoundConfig, getGraphDescendants, isDefaultNode, useAutoLayout, useGetViewportNodeIds, useGraphActions, useGraphActionsContext, useNodeVisibility };