@brainfish-ai/components 0.18.4 → 0.18.6

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.
Files changed (37) hide show
  1. package/dist/alert-dialog.d.ts +2 -2
  2. package/dist/article-suggestions-banner.d.ts +16 -0
  3. package/dist/button-group.d.ts +3 -2
  4. package/dist/button.d.ts +1 -1
  5. package/dist/combobox.d.ts +24 -3
  6. package/dist/confirm-dialog.d.ts +1 -1
  7. package/dist/div-button.d.ts +1 -1
  8. package/dist/esm/chunks/{ChatSearch.BJtS7ZMs.js → ChatSearch.DSB-T76c.js} +2 -2
  9. package/dist/esm/chunks/{ChatSearch.BJtS7ZMs.js.map → ChatSearch.DSB-T76c.js.map} +1 -1
  10. package/dist/esm/chunks/{combobox.CkN-wAHB.js → combobox.DNYCWyub.js} +40 -21
  11. package/dist/esm/chunks/combobox.DNYCWyub.js.map +1 -0
  12. package/dist/esm/components/article-suggestions-banner.js +55 -0
  13. package/dist/esm/components/article-suggestions-banner.js.map +1 -0
  14. package/dist/esm/components/chat-search.js +1 -1
  15. package/dist/esm/components/combobox.js +1 -1
  16. package/dist/esm/components/header-nav.js +55 -0
  17. package/dist/esm/components/header-nav.js.map +1 -0
  18. package/dist/esm/components/ui/button-group.js +23 -8
  19. package/dist/esm/components/ui/button-group.js.map +1 -1
  20. package/dist/esm/components/ui/button.js +2 -8
  21. package/dist/esm/components/ui/button.js.map +1 -1
  22. package/dist/esm/components/ui/command.js +1 -1
  23. package/dist/esm/components/ui/command.js.map +1 -1
  24. package/dist/esm/components/ui/div-button.js +1 -0
  25. package/dist/esm/components/ui/div-button.js.map +1 -1
  26. package/dist/esm/global.css +1 -1
  27. package/dist/esm/index.js +179 -5
  28. package/dist/esm/index.js.map +1 -1
  29. package/dist/header-nav.d.ts +28 -0
  30. package/dist/index.d.ts +134 -9
  31. package/dist/stats.html +1 -1
  32. package/dist/two-level-combobox.d.ts +2 -0
  33. package/package.json +6 -1
  34. package/components.json +0 -16
  35. package/dist/esm/chunks/combobox.CkN-wAHB.js.map +0 -1
  36. package/markdown-editor.plan.md +0 -2101
  37. package/markdown-editor.spec.md +0 -915
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/components/input-with-tags/input-with-tags.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Plus } from '@phosphor-icons/react';\n\nimport { cn } from '@/lib/utils';\nimport { Textarea } from '@/components/ui/textarea';\nimport { Button } from '@/components/ui/button';\n\nexport interface Tag {\n label: string;\n value: string;\n}\n\nexport interface InputWithTagsProps {\n title: string;\n placeholder?: string;\n tags: Tag[];\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n onTagClick?: (tag: Tag) => void;\n singleValue?: boolean; // If true, clicking a tag replaces the value instead of appending\n className?: string;\n inputClassName?: string;\n tagsClassName?: string;\n disabled?: boolean;\n // Standard textarea props\n name?: string;\n id?: string;\n required?: boolean;\n maxLength?: number;\n minLength?: number;\n rows?: number;\n}\n\nexport function InputWithTags({\n title,\n placeholder,\n tags,\n value: controlledValue,\n defaultValue,\n onChange,\n onTagClick,\n singleValue = false,\n className,\n inputClassName,\n tagsClassName,\n disabled,\n name,\n id,\n required,\n maxLength,\n minLength,\n rows,\n}: InputWithTagsProps) {\n // Helper to find tags present in value (tags are appended as: `${value} ${tag.value}`)\n const findTagsInValue = React.useCallback(\n (val: string): Set<string> => {\n if (!val) return new Set();\n const trimmedVal = val.trim();\n const foundTags = new Set<string>();\n\n for (const tag of tags) {\n const tagValue = tag.value.trim();\n if (singleValue) {\n if (trimmedVal === tagValue) {\n foundTags.add(tag.value);\n }\n } else {\n // Check if tag appears as complete segment (tags are appended as: `${value} ${tag.value}`)\n // Need to check: exact match, at start, in middle, or at end\n if (\n trimmedVal === tagValue ||\n trimmedVal.startsWith(tagValue + ' ') ||\n trimmedVal.includes(' ' + tagValue + ' ') ||\n trimmedVal.endsWith(' ' + tagValue)\n ) {\n foundTags.add(tag.value);\n }\n }\n }\n\n return foundTags;\n },\n [tags, singleValue],\n );\n\n // Initialize clickedTags based on initial value\n const initialValue = controlledValue !== undefined ? controlledValue : defaultValue || '';\n const [internalValue, setInternalValue] = React.useState(defaultValue || '');\n const [clickedTags, setClickedTags] = React.useState<Set<string>>(() => findTagsInValue(initialValue));\n\n // Use controlled value if provided, otherwise use internal state\n const value = controlledValue !== undefined ? controlledValue : internalValue;\n\n // Update clickedTags when value or tags change\n React.useEffect(() => {\n const currentValue = controlledValue !== undefined ? controlledValue : internalValue;\n setClickedTags(findTagsInValue(currentValue));\n }, [controlledValue, internalValue, tags, singleValue, findTagsInValue]);\n\n // Filter out clicked tags\n const availableTags = tags.filter((tag) => !clickedTags.has(tag.value));\n\n const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = e.target.value;\n if (controlledValue === undefined) {\n setInternalValue(newValue);\n // Sync clickedTags with the new value in uncontrolled mode\n setClickedTags(findTagsInValue(newValue));\n }\n onChange?.(newValue);\n };\n\n const handleTagClick = (tag: Tag) => {\n const newValue = singleValue ? tag.value : value ? `${value} ${tag.value}` : tag.value;\n if (controlledValue === undefined) {\n // In uncontrolled mode, update state and sync clickedTags immediately\n setInternalValue(newValue);\n // Sync clickedTags with the new value\n if (singleValue) {\n setClickedTags(new Set([tag.value]));\n } else {\n setClickedTags(findTagsInValue(newValue));\n }\n }\n // In controlled mode, only call onChange - let useEffect sync clickedTags when controlledValue updates\n onChange?.(newValue);\n onTagClick?.(tag);\n };\n\n return (\n <div className={cn('w-full flex flex-col gap-3', className)}>\n {title && (\n <label htmlFor={id} className=\"text-sm font-semibold\">\n {title}\n </label>\n )}\n <div className=\"relative w-full\">\n <Textarea\n id={id}\n name={name}\n placeholder={placeholder}\n value={value}\n onChange={handleInputChange}\n disabled={disabled}\n required={required}\n maxLength={maxLength}\n minLength={minLength}\n rows={rows}\n className={inputClassName}\n />\n </div>\n {availableTags.length > 0 && (\n <div className={cn('flex flex-wrap gap-2', tagsClassName)}>\n {availableTags.map((tag, index) => (\n <Button\n key={`${tag.value}-${index}`}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleTagClick(tag)}\n disabled={disabled}\n className=\"rounded-full\"\n >\n <Plus size={16} className=\"text-muted-foreground\" />\n <span>{tag.label}</span>\n </Button>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/components/input-with-tags/input-with-tags.tsx","../../src/components/font-picker/font-utils.ts","../../src/components/font-picker/use-font-options.ts","../../src/components/font-picker/font-picker-item.tsx","../../src/components/font-picker/font-picker.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Plus } from '@phosphor-icons/react';\n\nimport { cn } from '@/lib/utils';\nimport { Textarea } from '@/components/ui/textarea';\nimport { Button } from '@/components/ui/button';\n\nexport interface Tag {\n label: string;\n value: string;\n}\n\nexport interface InputWithTagsProps {\n title: string;\n placeholder?: string;\n tags: Tag[];\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n onTagClick?: (tag: Tag) => void;\n singleValue?: boolean; // If true, clicking a tag replaces the value instead of appending\n className?: string;\n inputClassName?: string;\n tagsClassName?: string;\n disabled?: boolean;\n // Standard textarea props\n name?: string;\n id?: string;\n required?: boolean;\n maxLength?: number;\n minLength?: number;\n rows?: number;\n}\n\nexport function InputWithTags({\n title,\n placeholder,\n tags,\n value: controlledValue,\n defaultValue,\n onChange,\n onTagClick,\n singleValue = false,\n className,\n inputClassName,\n tagsClassName,\n disabled,\n name,\n id,\n required,\n maxLength,\n minLength,\n rows,\n}: InputWithTagsProps) {\n // Helper to find tags present in value (tags are appended as: `${value} ${tag.value}`)\n const findTagsInValue = React.useCallback(\n (val: string): Set<string> => {\n if (!val) return new Set();\n const trimmedVal = val.trim();\n const foundTags = new Set<string>();\n\n for (const tag of tags) {\n const tagValue = tag.value.trim();\n if (singleValue) {\n if (trimmedVal === tagValue) {\n foundTags.add(tag.value);\n }\n } else {\n // Check if tag appears as complete segment (tags are appended as: `${value} ${tag.value}`)\n // Need to check: exact match, at start, in middle, or at end\n if (\n trimmedVal === tagValue ||\n trimmedVal.startsWith(tagValue + ' ') ||\n trimmedVal.includes(' ' + tagValue + ' ') ||\n trimmedVal.endsWith(' ' + tagValue)\n ) {\n foundTags.add(tag.value);\n }\n }\n }\n\n return foundTags;\n },\n [tags, singleValue],\n );\n\n // Initialize clickedTags based on initial value\n const initialValue = controlledValue !== undefined ? controlledValue : defaultValue || '';\n const [internalValue, setInternalValue] = React.useState(defaultValue || '');\n const [clickedTags, setClickedTags] = React.useState<Set<string>>(() => findTagsInValue(initialValue));\n\n // Use controlled value if provided, otherwise use internal state\n const value = controlledValue !== undefined ? controlledValue : internalValue;\n\n // Update clickedTags when value or tags change\n React.useEffect(() => {\n const currentValue = controlledValue !== undefined ? controlledValue : internalValue;\n setClickedTags(findTagsInValue(currentValue));\n }, [controlledValue, internalValue, tags, singleValue, findTagsInValue]);\n\n // Filter out clicked tags\n const availableTags = tags.filter((tag) => !clickedTags.has(tag.value));\n\n const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = e.target.value;\n if (controlledValue === undefined) {\n setInternalValue(newValue);\n // Sync clickedTags with the new value in uncontrolled mode\n setClickedTags(findTagsInValue(newValue));\n }\n onChange?.(newValue);\n };\n\n const handleTagClick = (tag: Tag) => {\n const newValue = singleValue ? tag.value : value ? `${value} ${tag.value}` : tag.value;\n if (controlledValue === undefined) {\n // In uncontrolled mode, update state and sync clickedTags immediately\n setInternalValue(newValue);\n // Sync clickedTags with the new value\n if (singleValue) {\n setClickedTags(new Set([tag.value]));\n } else {\n setClickedTags(findTagsInValue(newValue));\n }\n }\n // In controlled mode, only call onChange - let useEffect sync clickedTags when controlledValue updates\n onChange?.(newValue);\n onTagClick?.(tag);\n };\n\n return (\n <div className={cn('w-full flex flex-col gap-3', className)}>\n {title && (\n <label htmlFor={id} className=\"text-sm font-semibold\">\n {title}\n </label>\n )}\n <div className=\"relative w-full\">\n <Textarea\n id={id}\n name={name}\n placeholder={placeholder}\n value={value}\n onChange={handleInputChange}\n disabled={disabled}\n required={required}\n maxLength={maxLength}\n minLength={minLength}\n rows={rows}\n className={inputClassName}\n />\n </div>\n {availableTags.length > 0 && (\n <div className={cn('flex flex-wrap gap-2', tagsClassName)}>\n {availableTags.map((tag, index) => (\n <Button\n key={`${tag.value}-${index}`}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleTagClick(tag)}\n disabled={disabled}\n className=\"rounded-full\"\n >\n <Plus size={16} className=\"text-muted-foreground\" />\n <span>{tag.label}</span>\n </Button>\n ))}\n </div>\n )}\n </div>\n );\n}\n","import { FontOption } from './types';\n\n// Cache keys for localStorage\nconst FONT_CACHE_KEY = 'brainfish-google-fonts-cache';\nconst FONT_CACHE_TIMESTAMP_KEY = 'brainfish-google-fonts-cache-timestamp';\nconst FONT_CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours\n\n// Cache for loaded font stylesheets to prevent duplicate loads\nconst loadedFonts = new Set<string>();\n\n/**\n * Get cached fonts from localStorage\n */\nexport const getCachedFonts = (): FontOption[] | null => {\n try {\n const timestamp = localStorage.getItem(FONT_CACHE_TIMESTAMP_KEY);\n if (!timestamp) return null;\n\n const cacheAge = Date.now() - parseInt(timestamp, 10);\n if (cacheAge > FONT_CACHE_DURATION_MS) {\n localStorage.removeItem(FONT_CACHE_KEY);\n localStorage.removeItem(FONT_CACHE_TIMESTAMP_KEY);\n\n return null;\n }\n\n const cached = localStorage.getItem(FONT_CACHE_KEY);\n if (!cached) return null;\n\n return JSON.parse(cached) as FontOption[];\n } catch {\n return null;\n }\n};\n\n/**\n * Save fonts to localStorage cache\n */\nexport const setCachedFonts = (fonts: FontOption[]): void => {\n try {\n localStorage.setItem(FONT_CACHE_KEY, JSON.stringify(fonts));\n localStorage.setItem(FONT_CACHE_TIMESTAMP_KEY, Date.now().toString());\n } catch {\n // localStorage might be full or unavailable, ignore\n }\n};\n\n/**\n * Load a Google Font stylesheet dynamically\n */\nexport const loadFont = (fontFamily: string): void => {\n if (loadedFonts.has(fontFamily)) return;\n\n const link = document.createElement('link');\n link.href = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(/ /g, '+')}&display=swap`;\n link.rel = 'stylesheet';\n document.head.appendChild(link);\n loadedFonts.add(fontFamily);\n};\n\n/**\n * Clear the font cache from localStorage\n * Useful for forcing a fresh fetch of fonts\n */\nexport const clearFontCache = (): void => {\n try {\n localStorage.removeItem(FONT_CACHE_KEY);\n localStorage.removeItem(FONT_CACHE_TIMESTAMP_KEY);\n } catch {\n // localStorage might be unavailable, ignore\n }\n};\n","import * as React from 'react';\n\nimport { FontOption } from './types';\nimport { getCachedFonts, setCachedFonts, loadFont } from './font-utils';\n\nexport interface UseFontOptionsProps {\n apiKey: string;\n maxFonts?: number;\n onError?: (error: Error) => void;\n}\n\nexport interface UseFontOptionsReturn {\n fonts: FontOption[];\n isLoading: boolean;\n loadFont: typeof loadFont;\n}\n\n/**\n * Hook to fetch and manage Google Fonts options\n * Handles caching, loading state, and font stylesheet loading\n */\nexport function useFontOptions({ apiKey, maxFonts = 500, onError }: UseFontOptionsProps): UseFontOptionsReturn {\n const [fonts, setFonts] = React.useState<FontOption[]>([]);\n const [isLoading, setIsLoading] = React.useState(true);\n\n // Stabilize onError callback reference to prevent effect re-runs when\n // consumers pass inline functions (which create new references on every render)\n const onErrorRef = React.useRef(onError);\n React.useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n React.useEffect(() => {\n const fetchFonts = async () => {\n // Check cache first\n const cachedFonts = getCachedFonts();\n if (cachedFonts && cachedFonts.length > 0) {\n setFonts(cachedFonts.slice(0, maxFonts));\n setIsLoading(false);\n\n return;\n }\n\n try {\n const response = await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${apiKey}&sort=popularity`);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch fonts: ${response.statusText}`);\n }\n\n const data = await response.json();\n\n const fontOptions: FontOption[] = data.items.map((fontItem: { family: string; category: string; variants: string[] }) => ({\n value: fontItem.family,\n label: fontItem.family,\n category: fontItem.category,\n variants: fontItem.variants,\n }));\n\n // Cache all fonts, but only display up to maxFonts\n setCachedFonts(fontOptions);\n setFonts(fontOptions.slice(0, maxFonts));\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch fonts');\n // eslint-disable-next-line no-console\n console.error('FontPicker: Failed to fetch Google Fonts', error);\n onErrorRef.current?.(error);\n } finally {\n setIsLoading(false);\n }\n };\n\n void fetchFonts();\n }, [apiKey, maxFonts]);\n\n return { fonts, isLoading, loadFont };\n}\n","import * as React from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FontOption } from './types';\nimport { loadFont } from './font-utils';\n\nimport { cn } from '@/lib/utils';\nimport { CommandItem } from '@/components/ui/command';\n\nexport interface FontPickerItemProps {\n font: FontOption;\n isSelected: boolean;\n onSelect: () => void;\n}\n\n/**\n * Individual font item in the dropdown list\n * Loads font on mount for preview using Intersection Observer\n * Memoized to prevent unnecessary re-renders\n */\nexport const FontPickerItem = React.memo(function FontPickerItem({ font, isSelected, onSelect }: FontPickerItemProps) {\n const itemRef = React.useRef<HTMLDivElement>(null);\n const [isVisible, setIsVisible] = React.useState(false);\n\n // Use Intersection Observer to lazy-load fonts\n React.useEffect(() => {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setIsVisible(true);\n observer.disconnect();\n }\n },\n { threshold: 0.1 },\n );\n\n if (itemRef.current) {\n observer.observe(itemRef.current);\n }\n\n return () => observer.disconnect();\n }, []);\n\n // Load font when visible\n React.useEffect(() => {\n if (isVisible) {\n loadFont(font.value);\n }\n }, [isVisible, font.value]);\n\n // Build accessible label\n const ariaLabel = `Select ${font.label} font${font.category ? `, ${font.category} category` : ''}`;\n\n return (\n <CommandItem\n ref={itemRef}\n value={font.label}\n onSelect={onSelect}\n className=\"cursor-pointer\"\n aria-label={ariaLabel}\n aria-selected={isSelected}\n >\n <span style={{ fontFamily: isVisible ? font.value : undefined }} className=\"flex-1 truncate\">\n {font.label}\n </span>\n {font.category && <span className=\"text-xs text-muted-foreground capitalize\">{font.category}</span>}\n <Check className={cn('ml-2 h-4 w-4', isSelected ? 'opacity-100' : 'opacity-0')} />\n </CommandItem>\n );\n});\n","import * as React from 'react';\nimport { SpinnerGap } from '@phosphor-icons/react';\n\nimport { useFontOptions } from './use-font-options';\nimport { FontPickerItem } from './font-picker-item';\n\nimport { Combobox } from '@/components/combobox/combobox';\n\nexport interface FontPickerProps {\n /** Currently selected font family */\n value?: string;\n /** Callback when font selection changes */\n onChange?: (fontFamily: string) => void;\n /** Google Fonts API key (required) */\n apiKey: string;\n /** Placeholder text when no font is selected */\n placeholder?: string;\n /** Text shown in search input */\n searchPlaceholder?: string;\n /** Text shown when no fonts match the search */\n noResultsText?: string;\n /** Text shown while fonts are loading */\n loadingText?: string;\n /** Whether the picker is disabled */\n disabled?: boolean;\n /** Additional CSS classes for the trigger button */\n className?: string;\n /** Width of the dropdown (defaults to trigger width) */\n dropdownWidth?: number;\n /** Maximum number of fonts to display (for performance) */\n maxFonts?: number;\n /** Callback when an error occurs fetching fonts */\n onError?: (error: Error) => void;\n}\n\n/**\n * FontPicker - A searchable dropdown for selecting Google Fonts\n *\n * @example\n * ```tsx\n * const [font, setFont] = useState('Inter');\n *\n * <FontPicker\n * apiKey=\"YOUR_API_KEY\"\n * value={font}\n * onChange={setFont}\n * placeholder=\"Select a font\"\n * />\n * ```\n */\nexport function FontPicker({\n value,\n onChange,\n apiKey,\n placeholder = 'Select a font...',\n searchPlaceholder = 'Search fonts...',\n noResultsText = 'No fonts found.',\n loadingText = 'Loading fonts...',\n disabled = false,\n className,\n dropdownWidth,\n maxFonts = 500,\n onError,\n}: FontPickerProps) {\n const { fonts, isLoading, loadFont } = useFontOptions({ apiKey, maxFonts, onError });\n\n // Load font stylesheet when selected\n React.useEffect(() => {\n if (value) {\n loadFont(value);\n }\n }, [value, loadFont]);\n\n // fonts already have value/label which Combobox requires\n const options = fonts;\n\n return (\n <Combobox\n options={options}\n value={value}\n onChange={onChange}\n defaultValueLabel={placeholder}\n placeholder={searchPlaceholder}\n noResultsLabel={noResultsText}\n disabled={disabled || isLoading}\n className={className}\n dropdownWidth={dropdownWidth}\n renderTrigger={({ selectedItem }) => {\n if (isLoading) {\n return (\n <span className=\"flex items-center gap-2 text-muted-foreground\">\n <SpinnerGap className=\"h-4 w-4 animate-spin\" />\n {loadingText}\n </span>\n );\n }\n\n if (selectedItem) {\n return <span style={{ fontFamily: selectedItem.value }}>{selectedItem.label}</span>;\n }\n\n return <span className=\"text-muted-foreground\">{placeholder}</span>;\n }}\n renderItem={({ item, isSelected, onSelect }) => (\n <FontPickerItem font={item} isSelected={isSelected} onSelect={onSelect} />\n )}\n />\n );\n}\n\nexport default FontPicker;\n"],"names":["FontPickerItem"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
2
+ import * as React_2 from 'react';
3
+
4
+ export declare type AccountDropdownItem = {
5
+ id: string;
6
+ label: string;
7
+ onClick: () => void;
8
+ };
9
+
10
+ declare interface AccountDropdownProps extends React_2.ComponentPropsWithoutRef<typeof DropdownMenu> {
11
+ accountPhoto: string;
12
+ accountName: string;
13
+ accountDropdownItems: AccountDropdownItem[];
14
+ }
15
+
16
+ declare const DropdownMenu: React_2.FC<DropdownMenuPrimitive.DropdownMenuProps>;
17
+
18
+ export declare const HeaderNav: React_2.ForwardRefExoticComponent<HeaderNavProps & React_2.RefAttributes<HTMLElement>>;
19
+
20
+ declare interface HeaderNavProps extends React_2.ComponentPropsWithoutRef<'header'>, Omit<AccountDropdownProps, 'dir'> {
21
+ onSideNavigationToggle: () => void;
22
+ onHelpClick: () => void;
23
+ showSuggestionAction?: boolean;
24
+ suggestionAction?: React_2.ReactNode;
25
+ onSuggestionActionClick?: () => void;
26
+ }
27
+
28
+ export { }
package/dist/index.d.ts CHANGED
@@ -149,14 +149,14 @@ export declare const AlertDescription: React_2.ForwardRefExoticComponent<Omit<Re
149
149
  export declare const AlertDialog: React_2.ForwardRefExoticComponent<AlertDialogPrimitive.AlertDialogProps & React_2.RefAttributes<never>>;
150
150
 
151
151
  export declare const AlertDialogAction: React_2.ForwardRefExoticComponent<Omit<AlertDialogPrimitive.AlertDialogActionProps & React_2.RefAttributes<HTMLButtonElement>, "ref"> & VariantProps<(props?: ({
152
- variant?: "link" | "dark" | "default" | "destructive" | "success" | "outline" | "secondary" | "ghost" | "shadow" | "shadowSurface" | null | undefined;
152
+ variant?: "link" | "dark" | "default" | "destructive" | "success" | "outline" | "secondary" | "suggestion" | "ghost" | "shadow" | "shadowSurface" | null | undefined;
153
153
  size?: "default" | "icon" | "xs" | "sm" | "lg" | null | undefined;
154
154
  } & ClassProp) | undefined) => string> & {
155
155
  asChild?: boolean | undefined;
156
156
  } & React_2.RefAttributes<HTMLButtonElement>>;
157
157
 
158
158
  export declare const AlertDialogCancel: React_2.ForwardRefExoticComponent<Omit<AlertDialogPrimitive.AlertDialogCancelProps & React_2.RefAttributes<HTMLButtonElement>, "ref"> & VariantProps<(props?: ({
159
- variant?: "link" | "dark" | "default" | "destructive" | "success" | "outline" | "secondary" | "ghost" | "shadow" | "shadowSurface" | null | undefined;
159
+ variant?: "link" | "dark" | "default" | "destructive" | "success" | "outline" | "secondary" | "suggestion" | "ghost" | "shadow" | "shadowSurface" | null | undefined;
160
160
  size?: "default" | "icon" | "xs" | "sm" | "lg" | null | undefined;
161
161
  } & ClassProp) | undefined) => string> & React_2.RefAttributes<HTMLButtonElement>>;
162
162
 
@@ -424,16 +424,17 @@ export declare const BrainfishColors: {
424
424
 
425
425
  export declare const Button: React_2.ForwardRefExoticComponent<ButtonProps & React_2.RefAttributes<HTMLButtonElement>>;
426
426
 
427
- export declare function ButtonGroup({ className, orientation, ...props }: default_2.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>): default_2.JSX.Element;
427
+ export declare function ButtonGroup({ className, orientation, rounded, ...props }: default_2.ComponentProps<'div'> & VariantProps<typeof buttonGroupVariants>): default_2.JSX.Element;
428
428
 
429
429
  export declare function ButtonGroupSeparator({ className, orientation, ...props }: default_2.ComponentProps<typeof Separator>): default_2.JSX.Element;
430
430
 
431
- export declare function ButtonGroupText({ className, asChild, ...props }: default_2.ComponentProps<"div"> & {
431
+ export declare function ButtonGroupText({ className, asChild, ...props }: default_2.ComponentProps<'div'> & {
432
432
  asChild?: boolean;
433
433
  }): default_2.JSX.Element;
434
434
 
435
435
  export declare const buttonGroupVariants: (props?: ({
436
436
  orientation?: "horizontal" | "vertical" | null | undefined;
437
+ rounded?: boolean | null | undefined;
437
438
  } & ClassProp) | undefined) => string;
438
439
 
439
440
  export declare interface ButtonProps extends React_2.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -441,7 +442,7 @@ export declare interface ButtonProps extends React_2.ButtonHTMLAttributes<HTMLBu
441
442
  }
442
443
 
443
444
  export declare const buttonVariants: (props?: ({
444
- variant?: "link" | "dark" | "default" | "destructive" | "success" | "outline" | "secondary" | "ghost" | "shadow" | "shadowSurface" | null | undefined;
445
+ variant?: "link" | "dark" | "default" | "destructive" | "success" | "outline" | "secondary" | "suggestion" | "ghost" | "shadow" | "shadowSurface" | null | undefined;
445
446
  size?: "default" | "icon" | "xs" | "sm" | "lg" | null | undefined;
446
447
  } & ClassProp) | undefined) => string;
447
448
 
@@ -572,6 +573,12 @@ declare type ClearAll = {
572
573
  type: typeof AnswersActions.CLEAR_ALL;
573
574
  };
574
575
 
576
+ /**
577
+ * Clear the font cache from localStorage
578
+ * Useful for forcing a fresh fetch of fonts
579
+ */
580
+ export declare const clearFontCache: () => void;
581
+
575
582
  declare type ClearUserFeedback = {
576
583
  type: typeof AnswersActions.CLEAR_USER_FEEDBACK;
577
584
  payload: {
@@ -593,24 +600,45 @@ export declare interface Collection {
593
600
  siteEnabled?: boolean;
594
601
  }
595
602
 
596
- export declare function Combobox({ options, defaultValueLabel, placeholder, noResultsLabel, className, onOpenChange, onChange, dropdownWidth, value: controlledValue, }: ComboboxProps): React_2.JSX.Element;
603
+ export declare function Combobox<T extends ComboboxItem = ComboboxItem>({ options, defaultValueLabel, placeholder, noResultsLabel, className, onOpenChange, onChange, dropdownWidth, value: controlledValue, disabled, renderItem, renderValue, renderTrigger, }: ComboboxProps<T>): React_2.JSX.Element;
597
604
 
598
605
  export declare interface ComboboxItem {
599
606
  value: string;
600
607
  label: string;
601
608
  icon?: React_2.ReactNode;
609
+ /** Additional data that can be passed to custom renderers */
610
+ data?: any;
602
611
  }
603
612
 
604
- export declare interface ComboboxProps {
605
- options: ComboboxItem[];
613
+ export declare interface ComboboxProps<T extends ComboboxItem = ComboboxItem> {
614
+ options: T[];
606
615
  defaultValueLabel: string;
607
616
  placeholder: string;
608
617
  noResultsLabel: string;
609
618
  className?: string;
610
619
  dropdownWidth?: number;
611
620
  value?: string;
621
+ /** Whether the combobox is disabled */
622
+ disabled?: boolean;
623
+ /** Callback on open state change */
612
624
  onOpenChange?: (open: boolean) => void;
625
+ /** Callback on value change */
613
626
  onChange?: (value: string) => void;
627
+ /** Custom render function for each item in the dropdown */
628
+ renderItem?: (props: ComboboxRenderItemProps<T>) => React_2.ReactNode;
629
+ /** Custom render function for the selected value in the trigger button */
630
+ renderValue?: (item: T) => React_2.ReactNode;
631
+ /** Custom render function for the entire trigger button content */
632
+ renderTrigger?: (props: {
633
+ selectedItem: T | undefined;
634
+ isOpen: boolean;
635
+ }) => React_2.ReactNode;
636
+ }
637
+
638
+ export declare interface ComboboxRenderItemProps<T extends ComboboxItem = ComboboxItem> {
639
+ item: T;
640
+ isSelected: boolean;
641
+ onSelect: () => void;
614
642
  }
615
643
 
616
644
  export declare const Command: React_2.ForwardRefExoticComponent<Omit<{
@@ -762,7 +790,7 @@ export declare interface DivButtonProps extends React_2.HTMLAttributes<HTMLDivEl
762
790
  }
763
791
 
764
792
  export declare const divButtonVariants: (props?: ({
765
- variant?: "link" | "dark" | "default" | "destructive" | "outline" | "secondary" | "ghost" | "shadow" | "shadowSurface" | null | undefined;
793
+ variant?: "link" | "dark" | "default" | "destructive" | "outline" | "secondary" | "suggestion" | "ghost" | "shadow" | "shadowSurface" | null | undefined;
766
794
  size?: "default" | "icon" | "sm" | "lg" | null | undefined;
767
795
  } & ClassProp) | undefined) => string;
768
796
 
@@ -908,6 +936,70 @@ export declare interface FilterValue {
908
936
  value?: string | string[];
909
937
  }
910
938
 
939
+ export declare interface FontOption {
940
+ value: string;
941
+ label: string;
942
+ category?: string;
943
+ variants?: string[];
944
+ }
945
+
946
+ /**
947
+ * FontPicker - A searchable dropdown for selecting Google Fonts
948
+ *
949
+ * @example
950
+ * ```tsx
951
+ * const [font, setFont] = useState('Inter');
952
+ *
953
+ * <FontPicker
954
+ * apiKey="YOUR_API_KEY"
955
+ * value={font}
956
+ * onChange={setFont}
957
+ * placeholder="Select a font"
958
+ * />
959
+ * ```
960
+ */
961
+ export declare function FontPicker({ value, onChange, apiKey, placeholder, searchPlaceholder, noResultsText, loadingText, disabled, className, dropdownWidth, maxFonts, onError, }: FontPickerProps): React_2.JSX.Element;
962
+
963
+ /**
964
+ * Individual font item in the dropdown list
965
+ * Loads font on mount for preview using Intersection Observer
966
+ * Memoized to prevent unnecessary re-renders
967
+ */
968
+ export declare const FontPickerItem: React_2.NamedExoticComponent<FontPickerItemProps>;
969
+
970
+ export declare interface FontPickerItemProps {
971
+ font: FontOption;
972
+ isSelected: boolean;
973
+ onSelect: () => void;
974
+ }
975
+
976
+ export declare interface FontPickerProps {
977
+ /** Currently selected font family */
978
+ value?: string;
979
+ /** Callback when font selection changes */
980
+ onChange?: (fontFamily: string) => void;
981
+ /** Google Fonts API key (required) */
982
+ apiKey: string;
983
+ /** Placeholder text when no font is selected */
984
+ placeholder?: string;
985
+ /** Text shown in search input */
986
+ searchPlaceholder?: string;
987
+ /** Text shown when no fonts match the search */
988
+ noResultsText?: string;
989
+ /** Text shown while fonts are loading */
990
+ loadingText?: string;
991
+ /** Whether the picker is disabled */
992
+ disabled?: boolean;
993
+ /** Additional CSS classes for the trigger button */
994
+ className?: string;
995
+ /** Width of the dropdown (defaults to trigger width) */
996
+ dropdownWidth?: number;
997
+ /** Maximum number of fonts to display (for performance) */
998
+ maxFonts?: number;
999
+ /** Callback when an error occurs fetching fonts */
1000
+ onError?: (error: Error) => void;
1001
+ }
1002
+
911
1003
  export declare const formatFileSize: (bytes: number) => string;
912
1004
 
913
1005
  export declare const FormattedMessage: FC<Props>;
@@ -925,6 +1017,11 @@ declare type GetActionInputs = {
925
1017
  };
926
1018
  };
927
1019
 
1020
+ /**
1021
+ * Get cached fonts from localStorage
1022
+ */
1023
+ export declare const getCachedFonts: () => FontOption[] | null;
1024
+
928
1025
  /**
929
1026
  * A flexible icon component that supports both Phosphor icons and image-based icons.
930
1027
  *
@@ -1013,6 +1110,11 @@ declare type JSONSchema<T> = JSONSchemaType<T>;
1013
1110
 
1014
1111
  export declare const Label: React_2.ForwardRefExoticComponent<Omit<LabelPrimitive.LabelProps & React_2.RefAttributes<HTMLLabelElement>, "ref"> & VariantProps<(props?: ClassProp | undefined) => string> & React_2.RefAttributes<HTMLLabelElement>>;
1015
1112
 
1113
+ /**
1114
+ * Load a Google Font stylesheet dynamically
1115
+ */
1116
+ export declare const loadFont: (fontFamily: string) => void;
1117
+
1016
1118
  export declare const Logo: React_2.ForwardRefExoticComponent<Omit<LogoProps, "ref"> & React_2.RefAttributes<SVGSVGElement>>;
1017
1119
 
1018
1120
  export declare type LogoColor = 'default' | 'outline' | 'fill';
@@ -1181,6 +1283,11 @@ declare type SetAnswers = {
1181
1283
  };
1182
1284
  };
1183
1285
 
1286
+ /**
1287
+ * Save fonts to localStorage cache
1288
+ */
1289
+ export declare const setCachedFonts: (fonts: FontOption[]) => void;
1290
+
1184
1291
  declare type SetFollowUpQuestions = {
1185
1292
  type: typeof AnswersActions.SET_FOLLOW_UP_QUESTIONS;
1186
1293
  payload: {
@@ -1366,6 +1473,24 @@ declare type UrlString = string & {
1366
1473
 
1367
1474
  export declare const useChatSearch: () => ChatSearchContextProps;
1368
1475
 
1476
+ /**
1477
+ * Hook to fetch and manage Google Fonts options
1478
+ * Handles caching, loading state, and font stylesheet loading
1479
+ */
1480
+ export declare function useFontOptions({ apiKey, maxFonts, onError }: UseFontOptionsProps): UseFontOptionsReturn;
1481
+
1482
+ export declare interface UseFontOptionsProps {
1483
+ apiKey: string;
1484
+ maxFonts?: number;
1485
+ onError?: (error: Error) => void;
1486
+ }
1487
+
1488
+ export declare interface UseFontOptionsReturn {
1489
+ fonts: FontOption[];
1490
+ isLoading: boolean;
1491
+ loadFont: typeof loadFont;
1492
+ }
1493
+
1369
1494
  export declare const useIsChatSearchDirty: () => boolean;
1370
1495
 
1371
1496
  export declare const ZoomableImage: React_2.FC<ZoomableImageProps>;