@upbound/monarch-blocks 0.6.0 → 0.7.1
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.
- package/dist/cel-filter-bar.js +2 -32
- package/dist/cel-filter-bar.js.map +1 -1
- package/dist/chart.js +1 -31
- package/dist/chart.js.map +1 -1
- package/dist/chat-interface.js +1 -31
- package/dist/chat-interface.js.map +1 -1
- package/dist/code-block.d.ts +20 -5
- package/dist/code-block.js +71 -43
- package/dist/code-block.js.map +1 -1
- package/dist/data-table.d.ts +10 -1
- package/dist/data-table.js +10 -39
- package/dist/data-table.js.map +1 -1
- package/dist/filter-bar.js +2 -32
- package/dist/filter-bar.js.map +1 -1
- package/dist/floating-widget.js +1 -31
- package/dist/floating-widget.js.map +1 -1
- package/dist/graph.js +45 -68
- package/dist/graph.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +128 -92
- package/dist/index.js.map +1 -1
- package/dist/page-header.js +1 -31
- package/dist/page-header.js.map +1 -1
- package/dist/relative-time.js +1 -31
- package/dist/relative-time.js.map +1 -1
- package/dist/section-header.js +1 -31
- package/dist/section-header.js.map +1 -1
- package/dist/section-nav.js +1 -31
- package/dist/section-nav.js.map +1 -1
- package/dist/timeline.js +1 -31
- package/dist/timeline.js.map +1 -1
- package/package.json +4 -5
package/dist/filter-bar.js
CHANGED
|
@@ -35,37 +35,7 @@ import * as React from "react";
|
|
|
35
35
|
import { Icon } from "@upbound/monarch-core";
|
|
36
36
|
|
|
37
37
|
// src/lib/utils.ts
|
|
38
|
-
import {
|
|
39
|
-
import { extendTailwindMerge } from "tailwind-merge";
|
|
40
|
-
var twMerge = extendTailwindMerge({
|
|
41
|
-
extend: {
|
|
42
|
-
classGroups: {
|
|
43
|
-
"font-size": [
|
|
44
|
-
{
|
|
45
|
-
text: [
|
|
46
|
-
"display-hero",
|
|
47
|
-
"display-kpi-sm",
|
|
48
|
-
"display-kpi",
|
|
49
|
-
"display-kpi-lg",
|
|
50
|
-
"display-feature",
|
|
51
|
-
"h1",
|
|
52
|
-
"h2",
|
|
53
|
-
"h3",
|
|
54
|
-
"h4",
|
|
55
|
-
"body-lg",
|
|
56
|
-
"body",
|
|
57
|
-
"body-sm",
|
|
58
|
-
"caption",
|
|
59
|
-
"eyebrow"
|
|
60
|
-
]
|
|
61
|
-
}
|
|
62
|
-
]
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
function cn(...inputs) {
|
|
67
|
-
return twMerge(clsx(inputs));
|
|
68
|
-
}
|
|
38
|
+
import { cn } from "@upbound/monarch-core/utils";
|
|
69
39
|
function sortBy(items, getKey) {
|
|
70
40
|
return [...items].sort((a, b) => getKey(a).localeCompare(getKey(b)));
|
|
71
41
|
}
|
|
@@ -194,7 +164,7 @@ function FilterBar(_a) {
|
|
|
194
164
|
if (filter) handleAddFilter(filter.key);
|
|
195
165
|
},
|
|
196
166
|
children: [
|
|
197
|
-
/* @__PURE__ */ jsxs(ComboboxTrigger, { "aria-label": "Add filter", render: /* @__PURE__ */ jsx(Button, { variant: "outline" }), children: [
|
|
167
|
+
/* @__PURE__ */ jsxs(ComboboxTrigger, { "aria-label": "Add filter", showIndicator: false, render: /* @__PURE__ */ jsx(Button, { variant: "outline" }), children: [
|
|
198
168
|
/* @__PURE__ */ jsx(Icon, { name: "plus", "data-icon": "inline-start" }),
|
|
199
169
|
"Add filter"
|
|
200
170
|
] }),
|
package/dist/filter-bar.js.map
CHANGED
|
@@ -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, 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
|
+
{"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\" showIndicator={false} 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","export { cn } from '@upbound/monarch-core/utils';\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"]}
|
package/dist/floating-widget.js
CHANGED
|
@@ -4,37 +4,7 @@
|
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
6
|
// src/lib/utils.ts
|
|
7
|
-
import {
|
|
8
|
-
import { extendTailwindMerge } from "tailwind-merge";
|
|
9
|
-
var twMerge = extendTailwindMerge({
|
|
10
|
-
extend: {
|
|
11
|
-
classGroups: {
|
|
12
|
-
"font-size": [
|
|
13
|
-
{
|
|
14
|
-
text: [
|
|
15
|
-
"display-hero",
|
|
16
|
-
"display-kpi-sm",
|
|
17
|
-
"display-kpi",
|
|
18
|
-
"display-kpi-lg",
|
|
19
|
-
"display-feature",
|
|
20
|
-
"h1",
|
|
21
|
-
"h2",
|
|
22
|
-
"h3",
|
|
23
|
-
"h4",
|
|
24
|
-
"body-lg",
|
|
25
|
-
"body",
|
|
26
|
-
"body-sm",
|
|
27
|
-
"caption",
|
|
28
|
-
"eyebrow"
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
function cn(...inputs) {
|
|
36
|
-
return twMerge(clsx(inputs));
|
|
37
|
-
}
|
|
7
|
+
import { cn } from "@upbound/monarch-core/utils";
|
|
38
8
|
|
|
39
9
|
// src/floating-widget.tsx
|
|
40
10
|
import { Button } from "@upbound/monarch-core";
|
|
@@ -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\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":";;;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","export { cn } from '@upbound/monarch-core/utils';\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.js
CHANGED
|
@@ -53,37 +53,7 @@ import { getIncomers, getOutgoers, Handle, Position as Position2 } from "reactfl
|
|
|
53
53
|
import { Badge, Card, Icon, Spinner, Tooltip, TooltipContent, TooltipTrigger } from "@upbound/monarch-core";
|
|
54
54
|
|
|
55
55
|
// src/lib/utils.ts
|
|
56
|
-
import {
|
|
57
|
-
import { extendTailwindMerge } from "tailwind-merge";
|
|
58
|
-
var twMerge = extendTailwindMerge({
|
|
59
|
-
extend: {
|
|
60
|
-
classGroups: {
|
|
61
|
-
"font-size": [
|
|
62
|
-
{
|
|
63
|
-
text: [
|
|
64
|
-
"display-hero",
|
|
65
|
-
"display-kpi-sm",
|
|
66
|
-
"display-kpi",
|
|
67
|
-
"display-kpi-lg",
|
|
68
|
-
"display-feature",
|
|
69
|
-
"h1",
|
|
70
|
-
"h2",
|
|
71
|
-
"h3",
|
|
72
|
-
"h4",
|
|
73
|
-
"body-lg",
|
|
74
|
-
"body",
|
|
75
|
-
"body-sm",
|
|
76
|
-
"caption",
|
|
77
|
-
"eyebrow"
|
|
78
|
-
]
|
|
79
|
-
}
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
function cn(...inputs) {
|
|
85
|
-
return twMerge(clsx(inputs));
|
|
86
|
-
}
|
|
56
|
+
import { cn } from "@upbound/monarch-core/utils";
|
|
87
57
|
function sortBy(items, getKey) {
|
|
88
58
|
return [...items].sort((a, b) => getKey(a).localeCompare(getKey(b)));
|
|
89
59
|
}
|
|
@@ -459,7 +429,7 @@ var GraphActionsProvider = React2.forwardRef(
|
|
|
459
429
|
GraphActionsProvider.displayName = "GraphActionsProvider";
|
|
460
430
|
|
|
461
431
|
// src/graph/graph.tsx
|
|
462
|
-
import * as
|
|
432
|
+
import * as React7 from "react";
|
|
463
433
|
import {
|
|
464
434
|
getCoreRowModel,
|
|
465
435
|
getFacetedRowModel,
|
|
@@ -700,7 +670,7 @@ function FilterBar(_a) {
|
|
|
700
670
|
if (filter) handleAddFilter(filter.key);
|
|
701
671
|
},
|
|
702
672
|
children: [
|
|
703
|
-
/* @__PURE__ */ jsxs4(ComboboxTrigger, { "aria-label": "Add filter", render: /* @__PURE__ */ jsx5(Button2, { variant: "outline" }), children: [
|
|
673
|
+
/* @__PURE__ */ jsxs4(ComboboxTrigger, { "aria-label": "Add filter", showIndicator: false, render: /* @__PURE__ */ jsx5(Button2, { variant: "outline" }), children: [
|
|
704
674
|
/* @__PURE__ */ jsx5(Icon3, { name: "plus", "data-icon": "inline-start" }),
|
|
705
675
|
"Add filter"
|
|
706
676
|
] }),
|
|
@@ -1250,15 +1220,8 @@ function SectionHeaderTitle(_a) {
|
|
|
1250
1220
|
}
|
|
1251
1221
|
|
|
1252
1222
|
// src/graph/graph-view.tsx
|
|
1253
|
-
import * as
|
|
1254
|
-
import {
|
|
1255
|
-
Background,
|
|
1256
|
-
Controls,
|
|
1257
|
-
MiniMap,
|
|
1258
|
-
ReactFlow,
|
|
1259
|
-
ReactFlowProvider,
|
|
1260
|
-
useReactFlow
|
|
1261
|
-
} from "reactflow";
|
|
1223
|
+
import * as React6 from "react";
|
|
1224
|
+
import { Background, Controls, MiniMap, ReactFlow, ReactFlowProvider } from "reactflow";
|
|
1262
1225
|
|
|
1263
1226
|
// src/graph/hooks/useAutoLayout.ts
|
|
1264
1227
|
import { useMemo as useMemo3 } from "react";
|
|
@@ -1464,25 +1427,50 @@ var useNodeVisibility = (nodes, edges) => {
|
|
|
1464
1427
|
return { nodes: visibleNodes, edges: visibleEdges };
|
|
1465
1428
|
};
|
|
1466
1429
|
|
|
1430
|
+
// src/graph/hooks/useViewportFocus.ts
|
|
1431
|
+
import * as React5 from "react";
|
|
1432
|
+
import { useReactFlow, useStore } from "reactflow";
|
|
1433
|
+
function getViewportFocusCenter(node) {
|
|
1434
|
+
var _a;
|
|
1435
|
+
const compact = node.type !== "loading" && Boolean((_a = node.data) == null ? void 0 : _a.compact);
|
|
1436
|
+
const height = compact ? GRAPH_NODE_COLLAPSED_HEIGHT : GRAPH_NODE_HEIGHT;
|
|
1437
|
+
return {
|
|
1438
|
+
x: node.position.x + GRAPH_NODE_WIDTH / 2,
|
|
1439
|
+
y: node.position.y + height / 2
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function useViewportFocus(nodeMap, viewportFocusNodeId) {
|
|
1443
|
+
const { setCenter } = useReactFlow();
|
|
1444
|
+
const viewportReady = useStore((state) => state.width > 0 && state.height > 0);
|
|
1445
|
+
const node = viewportFocusNodeId ? nodeMap.get(viewportFocusNodeId) : void 0;
|
|
1446
|
+
React5.useEffect(() => {
|
|
1447
|
+
if (!node || !viewportReady) {
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
const { x, y } = getViewportFocusCenter(node);
|
|
1451
|
+
setCenter(x, y, { zoom: 1 });
|
|
1452
|
+
}, [node == null ? void 0 : node.id, viewportReady, setCenter]);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1467
1455
|
// src/graph/graph-view.tsx
|
|
1468
1456
|
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1469
1457
|
var GraphViewInternal = ({ nodeTypes: parentNodeTypes, edgeTypes: parentEdgeTypes, children, viewportFocusNodeId }) => {
|
|
1470
1458
|
const { nodes, edges, toggleNodeExpansion } = useGraphActionsContext();
|
|
1471
|
-
const nodeTypes =
|
|
1459
|
+
const nodeTypes = React6.useMemo(
|
|
1472
1460
|
() => __spreadValues({
|
|
1473
1461
|
default: GraphNode,
|
|
1474
1462
|
loading: GraphNodeSkeleton
|
|
1475
1463
|
}, parentNodeTypes),
|
|
1476
1464
|
[parentNodeTypes]
|
|
1477
1465
|
);
|
|
1478
|
-
const edgeTypes =
|
|
1466
|
+
const edgeTypes = React6.useMemo(
|
|
1479
1467
|
() => __spreadValues({
|
|
1480
1468
|
default: GraphEdge
|
|
1481
1469
|
}, parentEdgeTypes),
|
|
1482
1470
|
[parentEdgeTypes]
|
|
1483
1471
|
);
|
|
1484
1472
|
const { nodes: visibleNodes, edges: visibleEdges } = useNodeVisibility(nodes, edges);
|
|
1485
|
-
const nodesWithExpansion =
|
|
1473
|
+
const nodesWithExpansion = React6.useMemo(() => {
|
|
1486
1474
|
return visibleNodes.map((node) => {
|
|
1487
1475
|
if (isDefaultNode(node) && !node.data.onExpandToggle) {
|
|
1488
1476
|
return __spreadProps(__spreadValues({}, node), {
|
|
@@ -1529,21 +1517,10 @@ var GraphViewInternal = ({ nodeTypes: parentNodeTypes, edgeTypes: parentEdgeType
|
|
|
1529
1517
|
}
|
|
1530
1518
|
);
|
|
1531
1519
|
};
|
|
1532
|
-
var GraphView =
|
|
1520
|
+
var GraphView = React6.forwardRef(({ defaultNodes = [], defaultEdges = [], nodeTypes, edgeTypes, children, viewportFocusNodeId }, ref) => {
|
|
1533
1521
|
return /* @__PURE__ */ jsx8(ReactFlowProvider, { children: /* @__PURE__ */ jsx8(GraphActionsProvider, { ref, initialNodes: defaultNodes, initialEdges: defaultEdges, children: /* @__PURE__ */ jsx8(GraphViewInternal, { nodeTypes, edgeTypes, viewportFocusNodeId, children }) }) });
|
|
1534
1522
|
});
|
|
1535
1523
|
GraphView.displayName = "GraphView";
|
|
1536
|
-
function useViewportFocus(nodeMap, viewportFocusNodeId) {
|
|
1537
|
-
const flow = useReactFlow();
|
|
1538
|
-
React5.useEffect(() => {
|
|
1539
|
-
if (viewportFocusNodeId) {
|
|
1540
|
-
const focusNode = nodeMap.get(viewportFocusNodeId);
|
|
1541
|
-
if (focusNode) {
|
|
1542
|
-
flow.setCenter(focusNode.position.x, focusNode.position.y, { zoom: 1 });
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
}, [viewportFocusNodeId, flow, nodeMap]);
|
|
1546
|
-
}
|
|
1547
1524
|
|
|
1548
1525
|
// src/graph/graph.tsx
|
|
1549
1526
|
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
@@ -1582,12 +1559,12 @@ function Graph({
|
|
|
1582
1559
|
children
|
|
1583
1560
|
}) {
|
|
1584
1561
|
var _a, _b, _c;
|
|
1585
|
-
const graphRef =
|
|
1586
|
-
const [innerGlobalFilter, setInnerGlobalFilter] =
|
|
1587
|
-
const [innerColumnFilters, setInnerColumnFilters] =
|
|
1562
|
+
const graphRef = React7.useRef(null);
|
|
1563
|
+
const [innerGlobalFilter, setInnerGlobalFilter] = React7.useState("");
|
|
1564
|
+
const [innerColumnFilters, setInnerColumnFilters] = React7.useState([]);
|
|
1588
1565
|
const globalFilter = parentGlobalFilter != null ? parentGlobalFilter : innerGlobalFilter;
|
|
1589
1566
|
const setGlobalFilter = parentSetGlobalFilter != null ? parentSetGlobalFilter : setInnerGlobalFilter;
|
|
1590
|
-
|
|
1567
|
+
React7.useEffect(() => {
|
|
1591
1568
|
if (parentGlobalFilter !== void 0) {
|
|
1592
1569
|
setInnerGlobalFilter(parentGlobalFilter);
|
|
1593
1570
|
}
|
|
@@ -1608,7 +1585,7 @@ function Graph({
|
|
|
1608
1585
|
});
|
|
1609
1586
|
const flatColumns = table.getAllFlatColumns();
|
|
1610
1587
|
const filteredRows = table.getFilteredRowModel().rows;
|
|
1611
|
-
const filteredData =
|
|
1588
|
+
const filteredData = React7.useMemo(() => filteredRows.map((row) => row.original), [filteredRows]);
|
|
1612
1589
|
const filterCount = columnFilters.map(
|
|
1613
1590
|
({ id: columnFilterId, value }) => {
|
|
1614
1591
|
var _a2, _b2;
|
|
@@ -1622,10 +1599,10 @@ function Graph({
|
|
|
1622
1599
|
setColumnFilters([]);
|
|
1623
1600
|
setGlobalFilter("");
|
|
1624
1601
|
};
|
|
1625
|
-
const memoizedNodeTypes =
|
|
1626
|
-
const memoizedEdgeTypes =
|
|
1627
|
-
const memoizedConfig =
|
|
1628
|
-
|
|
1602
|
+
const memoizedNodeTypes = React7.useMemo(() => config.nodeTypes, [config.nodeTypes]);
|
|
1603
|
+
const memoizedEdgeTypes = React7.useMemo(() => config.edgeTypes, [config.edgeTypes]);
|
|
1604
|
+
const memoizedConfig = React7.useMemo(() => config, [config]);
|
|
1605
|
+
React7.useEffect(() => {
|
|
1629
1606
|
if (!graphRef.current) {
|
|
1630
1607
|
return;
|
|
1631
1608
|
}
|
|
@@ -1698,12 +1675,12 @@ function Graph({
|
|
|
1698
1675
|
|
|
1699
1676
|
// src/graph/hooks/useGetViewportNodeIds.ts
|
|
1700
1677
|
import { useEffect as useEffect3, useMemo as useMemo7, useRef as useRef3, useState as useState4 } from "react";
|
|
1701
|
-
import { useStore, useViewport } from "reactflow";
|
|
1678
|
+
import { useStore as useStore2, useViewport } from "reactflow";
|
|
1702
1679
|
var useGetDebouncedViewport = (debounceDelay = 1e3) => {
|
|
1703
1680
|
const timerRef = useRef3(null);
|
|
1704
1681
|
const [debouncedValues, setDebouncedValues] = useState4({ x: 0, y: 0, zoom: 1, width: 0, height: 0, nodes: [] });
|
|
1705
1682
|
const { x, y, zoom } = useViewport();
|
|
1706
|
-
const { width, height, nodes } =
|
|
1683
|
+
const { width, height, nodes } = useStore2((state) => ({
|
|
1707
1684
|
width: state.width,
|
|
1708
1685
|
height: state.height,
|
|
1709
1686
|
nodes: state.getNodes()
|