@brainfish-ai/components 0.33.1 → 0.34.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.
Files changed (83) hide show
  1. package/dist/conversation.d.ts +55 -12
  2. package/dist/esm/chunks/{ChatSearch.EPCaUj3O.js → ChatSearch.D5EABw51.js} +2 -2
  3. package/dist/esm/chunks/{ChatSearch.EPCaUj3O.js.map → ChatSearch.D5EABw51.js.map} +1 -1
  4. package/dist/esm/chunks/Conversation.Dq66Znf4.js +33 -0
  5. package/dist/esm/chunks/Conversation.Dq66Znf4.js.map +1 -0
  6. package/dist/esm/chunks/{FormattedMessage.D_knqVl-.js → FormattedMessage.Djy5cn5F.js} +2 -2
  7. package/dist/esm/chunks/{FormattedMessage.D_knqVl-.js.map → FormattedMessage.Djy5cn5F.js.map} +1 -1
  8. package/dist/esm/chunks/ai-badge.Be0978vU.js +5 -0
  9. package/dist/esm/chunks/ai-badge.Be0978vU.js.map +1 -0
  10. package/dist/esm/chunks/{button.D_2SonNs.js → button.Cs1X8upM.js} +2 -2
  11. package/dist/esm/chunks/{button.D_2SonNs.js.map → button.Cs1X8upM.js.map} +1 -1
  12. package/dist/esm/chunks/{combobox.MyoPH18G.js → combobox.CLT9CXUV.js} +2 -2
  13. package/dist/esm/chunks/{combobox.MyoPH18G.js.map → combobox.CLT9CXUV.js.map} +1 -1
  14. package/dist/esm/chunks/{date-picker.C2VT_rZ9.js → date-picker.D9CRxYJf.js} +2 -2
  15. package/dist/esm/chunks/{date-picker.C2VT_rZ9.js.map → date-picker.D9CRxYJf.js.map} +1 -1
  16. package/dist/esm/chunks/{feedback.CLMuSvsg.js → feedback.CGFAP0XS.js} +2 -2
  17. package/dist/esm/chunks/{feedback.CLMuSvsg.js.map → feedback.CGFAP0XS.js.map} +1 -1
  18. package/dist/esm/chunks/{file-upload-status.D8RhMcbO.js → file-upload-status.ClQeoQQB.js} +2 -2
  19. package/dist/esm/chunks/{file-upload-status.D8RhMcbO.js.map → file-upload-status.ClQeoQQB.js.map} +1 -1
  20. package/dist/esm/chunks/{filters.BHp3ukNW.js → filters.BRlRsGlZ.js} +2 -2
  21. package/dist/esm/chunks/{filters.BHp3ukNW.js.map → filters.BRlRsGlZ.js.map} +1 -1
  22. package/dist/esm/chunks/{font-picker.B9GPXyK4.js → font-picker.Dwwe5av-.js} +2 -2
  23. package/dist/esm/chunks/{font-picker.B9GPXyK4.js.map → font-picker.Dwwe5av-.js.map} +1 -1
  24. package/dist/esm/chunks/header-nav.CtUIc9pI.js +12 -0
  25. package/dist/esm/chunks/header-nav.CtUIc9pI.js.map +1 -0
  26. package/dist/esm/chunks/{header-pane.eU7f_pim.js → header-pane.BBWfo7uv.js} +5 -5
  27. package/dist/esm/chunks/header-pane.BBWfo7uv.js.map +1 -0
  28. package/dist/esm/chunks/index.Ca2JkOFO.js +2 -0
  29. package/dist/esm/chunks/index.Ca2JkOFO.js.map +1 -0
  30. package/dist/esm/chunks/{input-with-tags.DLv9e0XI.js → input-with-tags.pmOKv67d.js} +2 -2
  31. package/dist/esm/chunks/{input-with-tags.DLv9e0XI.js.map → input-with-tags.pmOKv67d.js.map} +1 -1
  32. package/dist/esm/chunks/{sidebar.CcFsNjrN.js → sidebar.BhcrMAU3.js} +8 -8
  33. package/dist/esm/chunks/{sidebar.CcFsNjrN.js.map → sidebar.BhcrMAU3.js.map} +1 -1
  34. package/dist/esm/chunks/{suggestion-card.D-AsG_yB.js → suggestion-card.TDn-dEc0.js} +2 -2
  35. package/dist/esm/chunks/{suggestion-card.D-AsG_yB.js.map → suggestion-card.TDn-dEc0.js.map} +1 -1
  36. package/dist/esm/chunks/{two-level-combobox.BVZfsNSN.js → two-level-combobox.SDv7qc_m.js} +2 -2
  37. package/dist/esm/chunks/{two-level-combobox.BVZfsNSN.js.map → two-level-combobox.SDv7qc_m.js.map} +1 -1
  38. package/dist/esm/components/article-suggestions-banner.js +1 -1
  39. package/dist/esm/components/articles-coverage.js +3 -3
  40. package/dist/esm/components/articles-updated.js +2 -2
  41. package/dist/esm/components/chat-search.js +1 -1
  42. package/dist/esm/components/combobox.js +1 -1
  43. package/dist/esm/components/conversation.js +1 -1
  44. package/dist/esm/components/convos.js +11 -11
  45. package/dist/esm/components/date-picker.js +1 -1
  46. package/dist/esm/components/feedback.js +1 -1
  47. package/dist/esm/components/file-upload.js +1 -1
  48. package/dist/esm/components/filter.js +1 -1
  49. package/dist/esm/components/font-picker.js +1 -1
  50. package/dist/esm/components/input-with-tags.js +1 -1
  51. package/dist/esm/components/markdown.js +1 -1
  52. package/dist/esm/components/two-level-combobox.js +1 -1
  53. package/dist/esm/components/ui/alert-dialog.js +1 -1
  54. package/dist/esm/components/ui/breadcrumb.js +1 -1
  55. package/dist/esm/components/ui/button-group.js +1 -1
  56. package/dist/esm/components/ui/button.js +1 -1
  57. package/dist/esm/components/ui/combobox.js +1 -1
  58. package/dist/esm/components/ui/div-button.js +1 -1
  59. package/dist/esm/components/ui/item.js +1 -1
  60. package/dist/esm/components/ui/sheet.js +1 -1
  61. package/dist/esm/components/ui/tooltip.js +3 -3
  62. package/dist/esm/components/ui/tooltip.js.map +1 -1
  63. package/dist/esm/global.css +1 -1
  64. package/dist/esm/index.js +1 -1
  65. package/dist/esm/layouts/full-layout.js +1 -1
  66. package/dist/esm/layouts/header-nav.js +1 -1
  67. package/dist/esm/layouts/sidebar.js +1 -1
  68. package/dist/esm/scenes/knowledge-review.js +1 -1
  69. package/dist/esm/tailwind.preset.js +1 -1
  70. package/dist/esm/tailwind.preset.js.map +1 -1
  71. package/dist/full-layout.d.ts +1 -0
  72. package/dist/header-nav.d.ts +1 -0
  73. package/dist/index.d.ts +56 -12
  74. package/dist/stats.html +1 -1
  75. package/package.json +7 -7
  76. package/tailwind.preset.ts +1 -1
  77. package/dist/esm/chunks/Conversation.C2TpUr4-.js +0 -29
  78. package/dist/esm/chunks/Conversation.C2TpUr4-.js.map +0 -1
  79. package/dist/esm/chunks/header-nav.DmlIUqml.js +0 -14
  80. package/dist/esm/chunks/header-nav.DmlIUqml.js.map +0 -1
  81. package/dist/esm/chunks/header-pane.eU7f_pim.js.map +0 -1
  82. package/dist/esm/chunks/index.uF4ME3WQ.js +0 -4
  83. package/dist/esm/chunks/index.uF4ME3WQ.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"filters.BHp3ukNW.js","sources":["../../../src/components/filter/types.ts","../../../src/components/filter/hooks.ts","../../../src/components/filter/components/CustomValueInput.tsx","../../../src/components/filter/components/MultiSelectDropdown.tsx","../../../src/components/filter/components/SingleSelectDropdown.tsx","../../../src/components/filter/filter.tsx","../../../src/components/filter/filters.tsx"],"sourcesContent":["// Types\nexport interface FilterOption {\n id: string;\n label: string;\n}\n\nexport enum Operator {\n IS = 'is',\n IS_NOT = 'is not',\n CONTAINS = 'contains',\n DOES_NOT_CONTAIN = 'does not contain',\n STARTS_WITH = 'starts with',\n}\n\nexport interface FilterValue {\n id: string | number;\n source?: string;\n operator?: Operator;\n value?: string | string[];\n}\n\n/**\n * Filter rules for each source\n * How this works:\n * - You can define the rules for each source\n * - The rules are used to determine the available operators and value type\n * - The input type is used to determine the type of input displayed in when the dropdown is opened\n * - The getValueOptions function is used to determine the available options for the value\n * - multiSelect determines if multiple values can be selected for dropdown type\n *\n * @example\n * {\n * 'sourceId': {\n * operators: [\n * { id: 'is', label: 'is' },\n * { id: 'is not', label: 'is not' },\n * ],\n * inputType: 'dropdown' | 'custom',\n * multiSelect?: boolean,\n * getValueOptions?: () => FilterOption[],\n * },\n * }\n */\nexport interface FilterRules {\n [sourceId: string]: {\n operators: FilterOption[];\n inputType: 'dropdown' | 'custom';\n multiSelect?: boolean;\n getValueOptions?: () => FilterOption[];\n };\n}\n\nexport interface FilterProps {\n filter: FilterValue;\n sourceOptions: FilterOption[];\n rules: FilterRules;\n onSourceChange?: (sourceId: string) => void;\n onRemoveFilter: (id: string | number) => void;\n onFilterChange: (filter: FilterValue) => void;\n selectPlaceholder?: string;\n customValuePlaceholder?: string;\n customValueButtonLabel?: string;\n sourceFirstMessage?: string;\n}\n","import { FilterValue } from './types';\n\nexport const useMultiSelectHelpers = (\n internalFilter: FilterValue,\n valueOptions: Array<{ id: string; label: string }>,\n selectPlaceholder: string,\n) => {\n const getSelectedValues = (): string[] => {\n if (Array.isArray(internalFilter.value)) {\n return internalFilter.value;\n }\n\n return internalFilter.value ? [internalFilter.value] : [];\n };\n\n const isValueSelected = (valueId: string): boolean => {\n const selectedValues = getSelectedValues();\n\n return selectedValues.includes(valueId);\n };\n\n const getDisplayValue = (): string => {\n const selectedValues = getSelectedValues();\n if (selectedValues.length === 0) return selectPlaceholder;\n if (selectedValues.length === 1) {\n const option = valueOptions.find((opt) => opt.id === selectedValues[0]);\n\n return option?.label || selectedValues[0];\n }\n\n return `${selectedValues.length} selected`;\n };\n\n return {\n getSelectedValues,\n isValueSelected,\n getDisplayValue,\n };\n};\n","import React, { useRef } from 'react';\n\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\n\ninterface CustomValueInputProps {\n inputValue: string;\n setInputValue: (value: string) => void;\n customValuePlaceholder: string;\n customValueButtonLabel: string;\n onSubmit: () => void;\n handleKeyDown: (e: React.KeyboardEvent<HTMLInputElement | HTMLButtonElement>, isInput: boolean) => void;\n}\n\nexport const CustomValueInput: React.FC<CustomValueInputProps> = ({\n inputValue,\n setInputValue,\n customValuePlaceholder,\n customValueButtonLabel,\n onSubmit,\n handleKeyDown,\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n\n return (\n <form\n className=\"p-3\"\n onSubmit={(e) => {\n e.preventDefault();\n onSubmit();\n }}\n >\n <Input\n ref={inputRef}\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder={customValuePlaceholder}\n className=\"mb-2\"\n onKeyDown={(e) => handleKeyDown(e, true)}\n />\n <Button\n type=\"submit\"\n variant=\"dark\"\n className=\"w-full\"\n onKeyDown={(e) => handleKeyDown(e, false)}\n data-custom-submit-button\n >\n {customValueButtonLabel}\n </Button>\n </form>\n );\n};\n","import React, { useState, useRef } from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FilterValue, FilterOption } from '../types';\n\nimport { Button } from '@/components/ui/button';\n\ninterface MultiSelectDropdownProps {\n internalFilter: FilterValue;\n valueOptions: FilterOption[];\n onConfirm: (selectedValues: string[]) => void;\n onClose: () => void;\n}\n\nconst getInitialValues = (value: FilterValue['value']): string[] => {\n if (Array.isArray(value)) {\n return value;\n } else if (value) {\n return [value];\n }\n\n return [];\n};\n\nexport const MultiSelectDropdown: React.FC<MultiSelectDropdownProps> = ({\n internalFilter,\n valueOptions,\n onConfirm,\n onClose,\n}) => {\n const [pendingValues, setPendingValues] = useState<string[]>(() => getInitialValues(internalFilter.value));\n const [announcement, setAnnouncement] = useState<string>('');\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n const applyButtonRef = useRef<HTMLButtonElement>(null);\n\n const handleValueToggle = (valueId: string) => {\n const option = valueOptions.find((opt) => opt.id === valueId);\n const isCurrentlySelected = pendingValues.includes(valueId);\n\n setPendingValues((prev) => {\n const newValues = prev.includes(valueId) ? prev.filter((id) => id !== valueId) : [...prev, valueId];\n\n // Announce the change\n if (option) {\n const action = isCurrentlySelected ? 'deselected' : 'selected';\n setAnnouncement(`${option.label} ${action}.`);\n }\n\n return newValues;\n });\n };\n\n const handleConfirm = () => {\n onConfirm(pendingValues);\n onClose();\n };\n\n const isValueSelected = (valueId: string): boolean => {\n return pendingValues.includes(valueId);\n };\n\n const focusButton = (index: number) => {\n if (index < valueOptions.length) {\n buttonRefs.current[index]?.focus();\n } else {\n applyButtonRef.current?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const nextIndex = Math.min(index + 1, valueOptions.length);\n focusButton(nextIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prevIndex = Math.max(index - 1, 0);\n focusButton(prevIndex);\n break;\n }\n case 'Enter':\n case 'Spacebar':\n e.preventDefault();\n if (index < valueOptions.length) {\n // If the user is selecting an option\n handleValueToggle(valueOptions[index].id);\n } else {\n // If the user is selecting the apply button\n handleConfirm();\n }\n break;\n case 'Escape':\n e.preventDefault();\n onClose();\n break;\n }\n };\n\n return (\n <div className=\"py-1\" role=\"listbox\" aria-multiselectable=\"true\">\n {/* Screen reader announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {announcement}\n </div>\n\n {valueOptions.map((option, index) => (\n <button\n key={option.id}\n ref={(el) => (buttonRefs.current[index] = el)}\n type=\"button\"\n className=\"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\"\n onClick={() => handleValueToggle(option.id)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n role=\"option\"\n aria-selected={isValueSelected(option.id)}\n >\n {isValueSelected(option.id) && <Check size={14} className=\"mr-2\" />}\n {!isValueSelected(option.id) && <span className=\"mr-2 w-3.5\" />}\n {option.label}\n </button>\n ))}\n\n <div className=\"border-t border-border mt-1 pt-2 px-2 pb-2\">\n <Button\n ref={applyButtonRef}\n onClick={handleConfirm}\n size=\"sm\"\n className=\"w-full\"\n disabled={pendingValues.length === 0}\n onKeyDown={(e) => handleKeyDown(e, valueOptions.length)}\n >\n Apply ({pendingValues.length} selected)\n </Button>\n </div>\n </div>\n );\n};\n","import React, { useRef } from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FilterOption } from '../types';\n\ninterface SingleSelectDropdownProps {\n valueOptions: FilterOption[];\n isValueSelected: (valueId: string) => boolean;\n onValueSelect: (valueId: string) => void;\n}\n\nexport const SingleSelectDropdown: React.FC<SingleSelectDropdownProps> = ({\n valueOptions,\n isValueSelected,\n onValueSelect,\n}) => {\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const focusButton = (index: number) => {\n buttonRefs.current[index]?.focus();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const nextIndex = Math.min(index + 1, valueOptions.length - 1);\n focusButton(nextIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prevIndex = Math.max(index - 1, 0);\n focusButton(prevIndex);\n break;\n }\n case 'Enter':\n case 'Spacebar':\n e.preventDefault();\n onValueSelect(valueOptions[index].id);\n break;\n }\n };\n\n return (\n <div className=\"py-1\" role=\"listbox\">\n {valueOptions.map((option, index) => (\n <button\n key={option.id}\n ref={(el) => (buttonRefs.current[index] = el)}\n type=\"button\"\n className=\"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\"\n onClick={() => onValueSelect(option.id)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n >\n {isValueSelected(option.id) && <Check size={14} className=\"mr-2\" />}\n {!isValueSelected(option.id) && <span className=\"mr-2 w-3.5\" />}\n {option.label}\n </button>\n ))}\n </div>\n );\n};\n","import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle } from 'react';\nimport { Check, XCircle } from '@phosphor-icons/react';\n\nimport { FilterProps, FilterValue, Operator } from './types';\nimport { useMultiSelectHelpers } from './hooks';\nimport { CustomValueInput } from './components/CustomValueInput';\nimport { MultiSelectDropdown } from './components/MultiSelectDropdown';\nimport { SingleSelectDropdown } from './components/SingleSelectDropdown';\n\nimport { Button } from '@/components/ui/button';\nimport { ButtonGroup } from '@/components/ui/button-group';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\n\nconst Filter = forwardRef<{ focus: () => void }, FilterProps>(\n (\n {\n filter,\n sourceOptions,\n rules,\n onSourceChange,\n onRemoveFilter,\n onFilterChange,\n selectPlaceholder = 'Select a value',\n customValuePlaceholder = 'Enter custom value',\n customValueButtonLabel = 'Done',\n sourceFirstMessage = 'Please select a source first',\n }: FilterProps,\n ref,\n ) => {\n // State\n const [internalFilter, setInternalFilter] = useState<FilterValue>({\n id: filter.id,\n source: filter.source,\n operator: filter.operator || Operator.IS,\n value: filter.value,\n });\n const [inputValue, setInputValue] = useState('');\n const [openDropdownType, setOpenDropdownType] = useState<'source' | 'operator' | 'value' | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const sourceButtonRef = useRef<HTMLButtonElement>(null);\n const divRef = useRef<HTMLDivElement>(null);\n\n // Expose focus method\n useImperativeHandle(ref, () => ({\n focus: () => {\n sourceButtonRef.current?.focus();\n },\n }));\n\n // Get current rules for selected source\n const currentRules = internalFilter.source ? rules[internalFilter.source] : undefined;\n const operatorOptions = currentRules?.operators || [];\n const isMultiSelect = currentRules?.multiSelect || false;\n let inputType = currentRules?.inputType || 'dropdown';\n if (internalFilter.operator === Operator.CONTAINS || internalFilter.operator === Operator.DOES_NOT_CONTAIN) {\n inputType = 'custom';\n }\n const valueOptions = currentRules?.getValueOptions ? currentRules.getValueOptions() : [];\n\n // Multi-select helpers\n const { getSelectedValues, isValueSelected, getDisplayValue } = useMultiSelectHelpers(\n internalFilter,\n valueOptions,\n selectPlaceholder,\n );\n\n // Auto-focus input when dropdown opens and custom input is needed\n useEffect(() => {\n if (openDropdownType === 'value' && inputType === 'custom') {\n const timeoutId = setTimeout(() => inputRef.current?.focus(), 50);\n\n return () => clearTimeout(timeoutId);\n }\n\n return undefined;\n }, [openDropdownType, inputType]);\n\n // Sync with external state\n useEffect(() => {\n setInternalFilter({\n id: filter.id,\n source: filter.source,\n operator: filter.operator || Operator.IS,\n value: filter.value,\n });\n }, [filter]);\n\n // Helper function to check if filter is complete and trigger onChange\n const checkAndTriggerChange = (newFilter: FilterValue) => {\n const hasValue = Array.isArray(newFilter.value) ? newFilter.value.length > 0 : !!newFilter.value?.trim();\n\n const isComplete = newFilter.source && newFilter.operator && hasValue;\n\n if (isComplete) {\n onFilterChange(newFilter);\n }\n };\n\n // Handle field updates\n const updateFilterField = (field: keyof FilterValue, value: string) => {\n const newFilter: FilterValue = { ...internalFilter, [field]: value };\n\n // Special handling for source changes\n if (field === 'source') {\n const sourceRules = rules[value];\n newFilter.operator = sourceRules?.operators[0]?.id as Operator;\n newFilter.value = sourceRules?.multiSelect ? [] : '';\n }\n // If operator changes, also clear value if inputType is dropdown\n if (field === 'operator') {\n if (internalFilter.source) {\n const sourceRules = rules[internalFilter.source];\n if (sourceRules?.inputType === 'dropdown') {\n newFilter.value = sourceRules?.multiSelect ? [] : '';\n }\n }\n }\n\n setInternalFilter(newFilter);\n checkAndTriggerChange(newFilter);\n\n if (!isMultiSelect || field !== 'value') {\n setOpenDropdownType(null);\n }\n };\n\n // Handle multi-select value changes\n const handleValueToggle = (valueId: string) => {\n const selectedValues = getSelectedValues();\n let newValues: string[];\n\n if (isMultiSelect) {\n if (selectedValues.includes(valueId)) {\n newValues = selectedValues.filter((id) => id !== valueId);\n } else {\n newValues = [...selectedValues, valueId];\n }\n } else {\n newValues = [valueId];\n }\n\n const newFilter = {\n ...internalFilter,\n value: isMultiSelect ? newValues : newValues[0] || '',\n };\n\n setInternalFilter(newFilter);\n checkAndTriggerChange(newFilter);\n\n // Close dropdown for single select, keep open for multi-select\n if (!isMultiSelect) {\n setOpenDropdownType(null);\n }\n };\n\n // Handle multi-select confirmation\n const handleMultiSelectConfirm = (selectedValues: string[]) => {\n const newFilter = {\n ...internalFilter,\n value: selectedValues,\n };\n\n setInternalFilter(newFilter);\n\n // Trigger onChange if filter is complete\n const isComplete = newFilter.source && newFilter.operator && selectedValues.length > 0;\n if (isComplete) onFilterChange(newFilter);\n\n setOpenDropdownType(null);\n };\n\n // Handle custom value submission\n const handleCustomValueSubmit = () => {\n if (inputValue.trim()) {\n const newFilter = { ...internalFilter, value: inputValue.trim() };\n setInternalFilter(newFilter);\n setInputValue('');\n setOpenDropdownType(null);\n if (newFilter.source && newFilter.operator && newFilter.value) {\n onFilterChange(newFilter);\n }\n }\n };\n\n // Tab trap handlers\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLButtonElement>, isInput: boolean) => {\n if (e.key === 'Escape') {\n setOpenDropdownType(null);\n\n return;\n }\n if (e.key === 'Tab') {\n if (isInput && !e.shiftKey) {\n e.preventDefault();\n const button = document.querySelector('[data-custom-submit-button]') as HTMLButtonElement;\n button?.focus();\n } else if (!isInput) {\n e.preventDefault();\n inputRef.current?.focus();\n }\n }\n };\n\n return (\n <ButtonGroup\n ref={divRef}\n orientation=\"horizontal\"\n className=\"rounded-full border border-border divide-x-2 divide-dark-500\"\n >\n {/* Source Dropdown */}\n <DropdownMenu\n open={openDropdownType === 'source'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'source' : null)}\n modal={false}\n >\n <DropdownMenuTrigger asChild>\n <Button ref={sourceButtonRef} variant=\"ghost\" size=\"xs\" className=\"rounded-l-full font-bold\">\n {sourceOptions.find((opt) => opt.id === internalFilter.source)?.label ||\n sourceOptions[0]?.label ||\n selectPlaceholder}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {sourceOptions.map((option) => (\n <DropdownMenuItem\n key={option.id}\n className=\"flex items-center\"\n onSelect={() => {\n updateFilterField('source', option.id);\n onSourceChange?.(option.id);\n }}\n >\n {option.id === internalFilter.source && <Check size={14} className=\"mr-2\" />}\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* Operator Dropdown */}\n <DropdownMenu\n open={openDropdownType === 'operator'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'operator' : null)}\n modal={false}\n >\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"xs\">\n {operatorOptions.find((opt) => opt.id === internalFilter.operator)?.label || selectPlaceholder}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {operatorOptions.map((option) => (\n <DropdownMenuItem\n key={option.id}\n className=\"flex items-center\"\n onSelect={() => updateFilterField('operator', option.id)}\n >\n {option.id === internalFilter.operator && <Check size={14} className=\"mr-2\" />}\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* Value Popover */}\n <Popover\n open={openDropdownType === 'value'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'value' : null)}\n >\n <PopoverTrigger asChild>\n <Button variant=\"ghost\" size=\"xs\" className=\"font-bold\">\n {getDisplayValue()}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-64 max-h-[min(320px,50vh)] overflow-y-auto p-0\">\n {inputType === 'custom' && (\n <div className=\"p-3\">\n <CustomValueInput\n inputValue={inputValue}\n setInputValue={setInputValue}\n customValuePlaceholder={customValuePlaceholder}\n customValueButtonLabel={customValueButtonLabel}\n onSubmit={handleCustomValueSubmit}\n handleKeyDown={handleKeyDown}\n />\n </div>\n )}\n {inputType === 'dropdown' && (\n <>\n {internalFilter.source ? (\n <>\n {isMultiSelect ? (\n <MultiSelectDropdown\n internalFilter={internalFilter}\n valueOptions={valueOptions}\n onConfirm={handleMultiSelectConfirm}\n onClose={() => setOpenDropdownType(null)}\n />\n ) : (\n <SingleSelectDropdown\n valueOptions={valueOptions}\n isValueSelected={isValueSelected}\n onValueSelect={handleValueToggle}\n />\n )}\n </>\n ) : (\n <div className=\"p-3 text-sm text-gray-500\">{sourceFirstMessage}</div>\n )}\n </>\n )}\n </PopoverContent>\n </Popover>\n\n {/* Remove button */}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n onClick={() => {\n setInternalFilter({ id: filter.id });\n setOpenDropdownType(null);\n onRemoveFilter(filter.id);\n }}\n className=\"p-2 hover:bg-black/10 rounded-none rounded-r-full\"\n aria-label=\"Remove filter\"\n >\n <XCircle size={16} weight=\"fill\" />\n </Button>\n </ButtonGroup>\n );\n },\n);\n\nFilter.displayName = 'Filter';\n\nexport { Filter };\n","import React, { useState, useEffect, useRef } from 'react';\nimport { Plus } from '@phosphor-icons/react';\nimport { createId } from '@paralleldrive/cuid2';\n\nimport { Filter, FilterOption, FilterRules, FilterValue, Operator } from './index';\nimport { Button } from '../ui/button';\n\nimport { cn } from '@/lib/utils';\n\ninterface FiltersProps extends React.HTMLAttributes<HTMLDivElement> {\n filters: FilterValue[];\n onFilterChange: (filter: FilterValue) => void;\n onFilterRemove: (filterId: string | number) => void;\n onFilterAdd?: (filter: FilterValue) => void;\n sourceOptions: FilterOption[];\n rules: FilterRules;\n}\n\nconst Filters = ({\n filters,\n onFilterChange,\n onFilterRemove,\n onFilterAdd,\n sourceOptions,\n rules,\n className,\n}: FiltersProps) => {\n const [internalFilters, setInternalFilters] = useState<FilterValue[]>(filters || []);\n const filterRefs = useRef<{ [key: string]: { focus: () => void } | null }>({});\n\n // Sync internal state with props\n useEffect(() => {\n setInternalFilters(filters || []);\n }, [filters]);\n\n // Get sources that are already in use\n const usedSources = internalFilters.map((filter) => filter.source).filter(Boolean);\n\n // Get available sources that haven't been used yet\n const availableSources = sourceOptions.filter((option) => {\n // If the source has multiSelect enabled, filter out used sources\n if (rules[option.id]?.multiSelect) {\n return !usedSources.includes(option.id);\n }\n\n // If multiSelect is false or undefined, allow the source to be used multiple times\n return sourceOptions;\n });\n\n // Check if we can add more filters\n const canAddFilter = availableSources.length > 0;\n\n const handleAddFilter = () => {\n if (!canAddFilter) return;\n\n const newFilter = {\n id: createId(),\n source: availableSources[0].id,\n operator: Operator.IS,\n value: rules[availableSources[0].id]?.multiSelect ? [] : '',\n };\n\n const newFilters = [...internalFilters, newFilter];\n setInternalFilters(newFilters);\n\n // Focus the new filter after it's rendered\n requestAnimationFrame(() => {\n filterRefs.current[newFilter.id]?.focus();\n });\n\n // Notify parent if callback provided\n onFilterAdd?.(newFilter);\n };\n\n const handleRemoveFilter = (filter: FilterValue) => {\n const newFilters = internalFilters.filter((f) => f.id !== filter.id);\n setInternalFilters(newFilters);\n onFilterRemove(filter.id);\n };\n\n return (\n <fieldset className={cn('flex flex-wrap gap-2', className)}>\n <legend className=\"sr-only\">Filters</legend>\n {internalFilters?.map((filter) => {\n // For each filter, include available sources plus its own current source\n const filterSourceOptions = sourceOptions.filter(\n (option) =>\n !usedSources.includes(option.id) || // Available sources\n option.id === filter.source, // Or its own current source\n );\n\n return (\n <Filter\n key={filter.id}\n ref={(ref) => {\n filterRefs.current[filter.id] = ref;\n }}\n filter={filter}\n sourceOptions={filterSourceOptions}\n rules={rules}\n onRemoveFilter={() => handleRemoveFilter(filter)}\n onFilterChange={onFilterChange}\n />\n );\n })}\n {canAddFilter && (\n <Button\n onClick={handleAddFilter}\n variant=\"ghost\"\n className=\"border border-foreground rounded-full gap-1\"\n size=\"sm\"\n title=\"Add filter\"\n >\n <Plus size={16} />\n Add filter\n </Button>\n )}\n </fieldset>\n );\n};\n\nexport { Filters };\n"],"names":["Operator","CustomValueInput","inputValue","setInputValue","customValuePlaceholder","customValueButtonLabel","onSubmit","handleKeyDown","inputRef","useRef","React","createElement","className","e","preventDefault","Input","ref","type","value","onChange","target","placeholder","onKeyDown","Button","variant","MultiSelectDropdown","internalFilter","valueOptions","onConfirm","onClose","pendingValues","setPendingValues","useState","getInitialValues","Array","isArray","announcement","setAnnouncement","buttonRefs","applyButtonRef","handleValueToggle","valueId","option","find","opt","id","isCurrentlySelected","includes","prev","newValues","filter","action","label","handleConfirm","isValueSelected","focusButton","index","length","current","focus","key","nextIndex","Math","min","prevIndex","max","role","map","el","onClick","Check","size","disabled","SingleSelectDropdown","onValueSelect","Filter","forwardRef","sourceOptions","rules","onSourceChange","onRemoveFilter","onFilterChange","selectPlaceholder","sourceFirstMessage","setInternalFilter","source","operator","IS","openDropdownType","setOpenDropdownType","sourceButtonRef","divRef","useImperativeHandle","currentRules","operatorOptions","operators","isMultiSelect","multiSelect","inputType","CONTAINS","DOES_NOT_CONTAIN","getValueOptions","getSelectedValues","getDisplayValue","selectedValues","useMultiSelectHelpers","useEffect","timeoutId","setTimeout","clearTimeout","checkAndTriggerChange","newFilter","hasValue","trim","updateFilterField","field","sourceRules","ButtonGroup","orientation","DropdownMenu","open","onOpenChange","modal","DropdownMenuTrigger","asChild","DropdownMenuContent","align","DropdownMenuItem","onSelect","Popover","PopoverTrigger","PopoverContent","isInput","shiftKey","button","document","querySelector","XCircle","weight","displayName","Filters","filters","onFilterRemove","onFilterAdd","internalFilters","setInternalFilters","filterRefs","usedSources","Boolean","availableSources","canAddFilter","cn","filterSourceOptions","newFilters","f","handleRemoveFilter","createId","requestAnimationFrame","title","Plus"],"mappings":"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"}
1
+ {"version":3,"file":"filters.BRlRsGlZ.js","sources":["../../../src/components/filter/types.ts","../../../src/components/filter/hooks.ts","../../../src/components/filter/components/CustomValueInput.tsx","../../../src/components/filter/components/MultiSelectDropdown.tsx","../../../src/components/filter/components/SingleSelectDropdown.tsx","../../../src/components/filter/filter.tsx","../../../src/components/filter/filters.tsx"],"sourcesContent":["// Types\nexport interface FilterOption {\n id: string;\n label: string;\n}\n\nexport enum Operator {\n IS = 'is',\n IS_NOT = 'is not',\n CONTAINS = 'contains',\n DOES_NOT_CONTAIN = 'does not contain',\n STARTS_WITH = 'starts with',\n}\n\nexport interface FilterValue {\n id: string | number;\n source?: string;\n operator?: Operator;\n value?: string | string[];\n}\n\n/**\n * Filter rules for each source\n * How this works:\n * - You can define the rules for each source\n * - The rules are used to determine the available operators and value type\n * - The input type is used to determine the type of input displayed in when the dropdown is opened\n * - The getValueOptions function is used to determine the available options for the value\n * - multiSelect determines if multiple values can be selected for dropdown type\n *\n * @example\n * {\n * 'sourceId': {\n * operators: [\n * { id: 'is', label: 'is' },\n * { id: 'is not', label: 'is not' },\n * ],\n * inputType: 'dropdown' | 'custom',\n * multiSelect?: boolean,\n * getValueOptions?: () => FilterOption[],\n * },\n * }\n */\nexport interface FilterRules {\n [sourceId: string]: {\n operators: FilterOption[];\n inputType: 'dropdown' | 'custom';\n multiSelect?: boolean;\n getValueOptions?: () => FilterOption[];\n };\n}\n\nexport interface FilterProps {\n filter: FilterValue;\n sourceOptions: FilterOption[];\n rules: FilterRules;\n onSourceChange?: (sourceId: string) => void;\n onRemoveFilter: (id: string | number) => void;\n onFilterChange: (filter: FilterValue) => void;\n selectPlaceholder?: string;\n customValuePlaceholder?: string;\n customValueButtonLabel?: string;\n sourceFirstMessage?: string;\n}\n","import { FilterValue } from './types';\n\nexport const useMultiSelectHelpers = (\n internalFilter: FilterValue,\n valueOptions: Array<{ id: string; label: string }>,\n selectPlaceholder: string,\n) => {\n const getSelectedValues = (): string[] => {\n if (Array.isArray(internalFilter.value)) {\n return internalFilter.value;\n }\n\n return internalFilter.value ? [internalFilter.value] : [];\n };\n\n const isValueSelected = (valueId: string): boolean => {\n const selectedValues = getSelectedValues();\n\n return selectedValues.includes(valueId);\n };\n\n const getDisplayValue = (): string => {\n const selectedValues = getSelectedValues();\n if (selectedValues.length === 0) return selectPlaceholder;\n if (selectedValues.length === 1) {\n const option = valueOptions.find((opt) => opt.id === selectedValues[0]);\n\n return option?.label || selectedValues[0];\n }\n\n return `${selectedValues.length} selected`;\n };\n\n return {\n getSelectedValues,\n isValueSelected,\n getDisplayValue,\n };\n};\n","import React, { useRef } from 'react';\n\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\n\ninterface CustomValueInputProps {\n inputValue: string;\n setInputValue: (value: string) => void;\n customValuePlaceholder: string;\n customValueButtonLabel: string;\n onSubmit: () => void;\n handleKeyDown: (e: React.KeyboardEvent<HTMLInputElement | HTMLButtonElement>, isInput: boolean) => void;\n}\n\nexport const CustomValueInput: React.FC<CustomValueInputProps> = ({\n inputValue,\n setInputValue,\n customValuePlaceholder,\n customValueButtonLabel,\n onSubmit,\n handleKeyDown,\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n\n return (\n <form\n className=\"p-3\"\n onSubmit={(e) => {\n e.preventDefault();\n onSubmit();\n }}\n >\n <Input\n ref={inputRef}\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder={customValuePlaceholder}\n className=\"mb-2\"\n onKeyDown={(e) => handleKeyDown(e, true)}\n />\n <Button\n type=\"submit\"\n variant=\"dark\"\n className=\"w-full\"\n onKeyDown={(e) => handleKeyDown(e, false)}\n data-custom-submit-button\n >\n {customValueButtonLabel}\n </Button>\n </form>\n );\n};\n","import React, { useState, useRef } from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FilterValue, FilterOption } from '../types';\n\nimport { Button } from '@/components/ui/button';\n\ninterface MultiSelectDropdownProps {\n internalFilter: FilterValue;\n valueOptions: FilterOption[];\n onConfirm: (selectedValues: string[]) => void;\n onClose: () => void;\n}\n\nconst getInitialValues = (value: FilterValue['value']): string[] => {\n if (Array.isArray(value)) {\n return value;\n } else if (value) {\n return [value];\n }\n\n return [];\n};\n\nexport const MultiSelectDropdown: React.FC<MultiSelectDropdownProps> = ({\n internalFilter,\n valueOptions,\n onConfirm,\n onClose,\n}) => {\n const [pendingValues, setPendingValues] = useState<string[]>(() => getInitialValues(internalFilter.value));\n const [announcement, setAnnouncement] = useState<string>('');\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n const applyButtonRef = useRef<HTMLButtonElement>(null);\n\n const handleValueToggle = (valueId: string) => {\n const option = valueOptions.find((opt) => opt.id === valueId);\n const isCurrentlySelected = pendingValues.includes(valueId);\n\n setPendingValues((prev) => {\n const newValues = prev.includes(valueId) ? prev.filter((id) => id !== valueId) : [...prev, valueId];\n\n // Announce the change\n if (option) {\n const action = isCurrentlySelected ? 'deselected' : 'selected';\n setAnnouncement(`${option.label} ${action}.`);\n }\n\n return newValues;\n });\n };\n\n const handleConfirm = () => {\n onConfirm(pendingValues);\n onClose();\n };\n\n const isValueSelected = (valueId: string): boolean => {\n return pendingValues.includes(valueId);\n };\n\n const focusButton = (index: number) => {\n if (index < valueOptions.length) {\n buttonRefs.current[index]?.focus();\n } else {\n applyButtonRef.current?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const nextIndex = Math.min(index + 1, valueOptions.length);\n focusButton(nextIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prevIndex = Math.max(index - 1, 0);\n focusButton(prevIndex);\n break;\n }\n case 'Enter':\n case 'Spacebar':\n e.preventDefault();\n if (index < valueOptions.length) {\n // If the user is selecting an option\n handleValueToggle(valueOptions[index].id);\n } else {\n // If the user is selecting the apply button\n handleConfirm();\n }\n break;\n case 'Escape':\n e.preventDefault();\n onClose();\n break;\n }\n };\n\n return (\n <div className=\"py-1\" role=\"listbox\" aria-multiselectable=\"true\">\n {/* Screen reader announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {announcement}\n </div>\n\n {valueOptions.map((option, index) => (\n <button\n key={option.id}\n ref={(el) => (buttonRefs.current[index] = el)}\n type=\"button\"\n className=\"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\"\n onClick={() => handleValueToggle(option.id)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n role=\"option\"\n aria-selected={isValueSelected(option.id)}\n >\n {isValueSelected(option.id) && <Check size={14} className=\"mr-2\" />}\n {!isValueSelected(option.id) && <span className=\"mr-2 w-3.5\" />}\n {option.label}\n </button>\n ))}\n\n <div className=\"border-t border-border mt-1 pt-2 px-2 pb-2\">\n <Button\n ref={applyButtonRef}\n onClick={handleConfirm}\n size=\"sm\"\n className=\"w-full\"\n disabled={pendingValues.length === 0}\n onKeyDown={(e) => handleKeyDown(e, valueOptions.length)}\n >\n Apply ({pendingValues.length} selected)\n </Button>\n </div>\n </div>\n );\n};\n","import React, { useRef } from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FilterOption } from '../types';\n\ninterface SingleSelectDropdownProps {\n valueOptions: FilterOption[];\n isValueSelected: (valueId: string) => boolean;\n onValueSelect: (valueId: string) => void;\n}\n\nexport const SingleSelectDropdown: React.FC<SingleSelectDropdownProps> = ({\n valueOptions,\n isValueSelected,\n onValueSelect,\n}) => {\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const focusButton = (index: number) => {\n buttonRefs.current[index]?.focus();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const nextIndex = Math.min(index + 1, valueOptions.length - 1);\n focusButton(nextIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prevIndex = Math.max(index - 1, 0);\n focusButton(prevIndex);\n break;\n }\n case 'Enter':\n case 'Spacebar':\n e.preventDefault();\n onValueSelect(valueOptions[index].id);\n break;\n }\n };\n\n return (\n <div className=\"py-1\" role=\"listbox\">\n {valueOptions.map((option, index) => (\n <button\n key={option.id}\n ref={(el) => (buttonRefs.current[index] = el)}\n type=\"button\"\n className=\"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\"\n onClick={() => onValueSelect(option.id)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n >\n {isValueSelected(option.id) && <Check size={14} className=\"mr-2\" />}\n {!isValueSelected(option.id) && <span className=\"mr-2 w-3.5\" />}\n {option.label}\n </button>\n ))}\n </div>\n );\n};\n","import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle } from 'react';\nimport { Check, XCircle } from '@phosphor-icons/react';\n\nimport { FilterProps, FilterValue, Operator } from './types';\nimport { useMultiSelectHelpers } from './hooks';\nimport { CustomValueInput } from './components/CustomValueInput';\nimport { MultiSelectDropdown } from './components/MultiSelectDropdown';\nimport { SingleSelectDropdown } from './components/SingleSelectDropdown';\n\nimport { Button } from '@/components/ui/button';\nimport { ButtonGroup } from '@/components/ui/button-group';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\n\nconst Filter = forwardRef<{ focus: () => void }, FilterProps>(\n (\n {\n filter,\n sourceOptions,\n rules,\n onSourceChange,\n onRemoveFilter,\n onFilterChange,\n selectPlaceholder = 'Select a value',\n customValuePlaceholder = 'Enter custom value',\n customValueButtonLabel = 'Done',\n sourceFirstMessage = 'Please select a source first',\n }: FilterProps,\n ref,\n ) => {\n // State\n const [internalFilter, setInternalFilter] = useState<FilterValue>({\n id: filter.id,\n source: filter.source,\n operator: filter.operator || Operator.IS,\n value: filter.value,\n });\n const [inputValue, setInputValue] = useState('');\n const [openDropdownType, setOpenDropdownType] = useState<'source' | 'operator' | 'value' | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const sourceButtonRef = useRef<HTMLButtonElement>(null);\n const divRef = useRef<HTMLDivElement>(null);\n\n // Expose focus method\n useImperativeHandle(ref, () => ({\n focus: () => {\n sourceButtonRef.current?.focus();\n },\n }));\n\n // Get current rules for selected source\n const currentRules = internalFilter.source ? rules[internalFilter.source] : undefined;\n const operatorOptions = currentRules?.operators || [];\n const isMultiSelect = currentRules?.multiSelect || false;\n let inputType = currentRules?.inputType || 'dropdown';\n if (internalFilter.operator === Operator.CONTAINS || internalFilter.operator === Operator.DOES_NOT_CONTAIN) {\n inputType = 'custom';\n }\n const valueOptions = currentRules?.getValueOptions ? currentRules.getValueOptions() : [];\n\n // Multi-select helpers\n const { getSelectedValues, isValueSelected, getDisplayValue } = useMultiSelectHelpers(\n internalFilter,\n valueOptions,\n selectPlaceholder,\n );\n\n // Auto-focus input when dropdown opens and custom input is needed\n useEffect(() => {\n if (openDropdownType === 'value' && inputType === 'custom') {\n const timeoutId = setTimeout(() => inputRef.current?.focus(), 50);\n\n return () => clearTimeout(timeoutId);\n }\n\n return undefined;\n }, [openDropdownType, inputType]);\n\n // Sync with external state\n useEffect(() => {\n setInternalFilter({\n id: filter.id,\n source: filter.source,\n operator: filter.operator || Operator.IS,\n value: filter.value,\n });\n }, [filter]);\n\n // Helper function to check if filter is complete and trigger onChange\n const checkAndTriggerChange = (newFilter: FilterValue) => {\n const hasValue = Array.isArray(newFilter.value) ? newFilter.value.length > 0 : !!newFilter.value?.trim();\n\n const isComplete = newFilter.source && newFilter.operator && hasValue;\n\n if (isComplete) {\n onFilterChange(newFilter);\n }\n };\n\n // Handle field updates\n const updateFilterField = (field: keyof FilterValue, value: string) => {\n const newFilter: FilterValue = { ...internalFilter, [field]: value };\n\n // Special handling for source changes\n if (field === 'source') {\n const sourceRules = rules[value];\n newFilter.operator = sourceRules?.operators[0]?.id as Operator;\n newFilter.value = sourceRules?.multiSelect ? [] : '';\n }\n // If operator changes, also clear value if inputType is dropdown\n if (field === 'operator') {\n if (internalFilter.source) {\n const sourceRules = rules[internalFilter.source];\n if (sourceRules?.inputType === 'dropdown') {\n newFilter.value = sourceRules?.multiSelect ? [] : '';\n }\n }\n }\n\n setInternalFilter(newFilter);\n checkAndTriggerChange(newFilter);\n\n if (!isMultiSelect || field !== 'value') {\n setOpenDropdownType(null);\n }\n };\n\n // Handle multi-select value changes\n const handleValueToggle = (valueId: string) => {\n const selectedValues = getSelectedValues();\n let newValues: string[];\n\n if (isMultiSelect) {\n if (selectedValues.includes(valueId)) {\n newValues = selectedValues.filter((id) => id !== valueId);\n } else {\n newValues = [...selectedValues, valueId];\n }\n } else {\n newValues = [valueId];\n }\n\n const newFilter = {\n ...internalFilter,\n value: isMultiSelect ? newValues : newValues[0] || '',\n };\n\n setInternalFilter(newFilter);\n checkAndTriggerChange(newFilter);\n\n // Close dropdown for single select, keep open for multi-select\n if (!isMultiSelect) {\n setOpenDropdownType(null);\n }\n };\n\n // Handle multi-select confirmation\n const handleMultiSelectConfirm = (selectedValues: string[]) => {\n const newFilter = {\n ...internalFilter,\n value: selectedValues,\n };\n\n setInternalFilter(newFilter);\n\n // Trigger onChange if filter is complete\n const isComplete = newFilter.source && newFilter.operator && selectedValues.length > 0;\n if (isComplete) onFilterChange(newFilter);\n\n setOpenDropdownType(null);\n };\n\n // Handle custom value submission\n const handleCustomValueSubmit = () => {\n if (inputValue.trim()) {\n const newFilter = { ...internalFilter, value: inputValue.trim() };\n setInternalFilter(newFilter);\n setInputValue('');\n setOpenDropdownType(null);\n if (newFilter.source && newFilter.operator && newFilter.value) {\n onFilterChange(newFilter);\n }\n }\n };\n\n // Tab trap handlers\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLButtonElement>, isInput: boolean) => {\n if (e.key === 'Escape') {\n setOpenDropdownType(null);\n\n return;\n }\n if (e.key === 'Tab') {\n if (isInput && !e.shiftKey) {\n e.preventDefault();\n const button = document.querySelector('[data-custom-submit-button]') as HTMLButtonElement;\n button?.focus();\n } else if (!isInput) {\n e.preventDefault();\n inputRef.current?.focus();\n }\n }\n };\n\n return (\n <ButtonGroup\n ref={divRef}\n orientation=\"horizontal\"\n className=\"rounded-full border border-border divide-x-2 divide-dark-500\"\n >\n {/* Source Dropdown */}\n <DropdownMenu\n open={openDropdownType === 'source'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'source' : null)}\n modal={false}\n >\n <DropdownMenuTrigger asChild>\n <Button ref={sourceButtonRef} variant=\"ghost\" size=\"xs\" className=\"rounded-l-full font-bold\">\n {sourceOptions.find((opt) => opt.id === internalFilter.source)?.label ||\n sourceOptions[0]?.label ||\n selectPlaceholder}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {sourceOptions.map((option) => (\n <DropdownMenuItem\n key={option.id}\n className=\"flex items-center\"\n onSelect={() => {\n updateFilterField('source', option.id);\n onSourceChange?.(option.id);\n }}\n >\n {option.id === internalFilter.source && <Check size={14} className=\"mr-2\" />}\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* Operator Dropdown */}\n <DropdownMenu\n open={openDropdownType === 'operator'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'operator' : null)}\n modal={false}\n >\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"xs\">\n {operatorOptions.find((opt) => opt.id === internalFilter.operator)?.label || selectPlaceholder}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {operatorOptions.map((option) => (\n <DropdownMenuItem\n key={option.id}\n className=\"flex items-center\"\n onSelect={() => updateFilterField('operator', option.id)}\n >\n {option.id === internalFilter.operator && <Check size={14} className=\"mr-2\" />}\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* Value Popover */}\n <Popover\n open={openDropdownType === 'value'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'value' : null)}\n >\n <PopoverTrigger asChild>\n <Button variant=\"ghost\" size=\"xs\" className=\"font-bold\">\n {getDisplayValue()}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-64 max-h-[min(320px,50vh)] overflow-y-auto p-0\">\n {inputType === 'custom' && (\n <div className=\"p-3\">\n <CustomValueInput\n inputValue={inputValue}\n setInputValue={setInputValue}\n customValuePlaceholder={customValuePlaceholder}\n customValueButtonLabel={customValueButtonLabel}\n onSubmit={handleCustomValueSubmit}\n handleKeyDown={handleKeyDown}\n />\n </div>\n )}\n {inputType === 'dropdown' && (\n <>\n {internalFilter.source ? (\n <>\n {isMultiSelect ? (\n <MultiSelectDropdown\n internalFilter={internalFilter}\n valueOptions={valueOptions}\n onConfirm={handleMultiSelectConfirm}\n onClose={() => setOpenDropdownType(null)}\n />\n ) : (\n <SingleSelectDropdown\n valueOptions={valueOptions}\n isValueSelected={isValueSelected}\n onValueSelect={handleValueToggle}\n />\n )}\n </>\n ) : (\n <div className=\"p-3 text-sm text-gray-500\">{sourceFirstMessage}</div>\n )}\n </>\n )}\n </PopoverContent>\n </Popover>\n\n {/* Remove button */}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n onClick={() => {\n setInternalFilter({ id: filter.id });\n setOpenDropdownType(null);\n onRemoveFilter(filter.id);\n }}\n className=\"p-2 hover:bg-black/10 rounded-none rounded-r-full\"\n aria-label=\"Remove filter\"\n >\n <XCircle size={16} weight=\"fill\" />\n </Button>\n </ButtonGroup>\n );\n },\n);\n\nFilter.displayName = 'Filter';\n\nexport { Filter };\n","import React, { useState, useEffect, useRef } from 'react';\nimport { Plus } from '@phosphor-icons/react';\nimport { createId } from '@paralleldrive/cuid2';\n\nimport { Filter, FilterOption, FilterRules, FilterValue, Operator } from './index';\nimport { Button } from '../ui/button';\n\nimport { cn } from '@/lib/utils';\n\ninterface FiltersProps extends React.HTMLAttributes<HTMLDivElement> {\n filters: FilterValue[];\n onFilterChange: (filter: FilterValue) => void;\n onFilterRemove: (filterId: string | number) => void;\n onFilterAdd?: (filter: FilterValue) => void;\n sourceOptions: FilterOption[];\n rules: FilterRules;\n}\n\nconst Filters = ({\n filters,\n onFilterChange,\n onFilterRemove,\n onFilterAdd,\n sourceOptions,\n rules,\n className,\n}: FiltersProps) => {\n const [internalFilters, setInternalFilters] = useState<FilterValue[]>(filters || []);\n const filterRefs = useRef<{ [key: string]: { focus: () => void } | null }>({});\n\n // Sync internal state with props\n useEffect(() => {\n setInternalFilters(filters || []);\n }, [filters]);\n\n // Get sources that are already in use\n const usedSources = internalFilters.map((filter) => filter.source).filter(Boolean);\n\n // Get available sources that haven't been used yet\n const availableSources = sourceOptions.filter((option) => {\n // If the source has multiSelect enabled, filter out used sources\n if (rules[option.id]?.multiSelect) {\n return !usedSources.includes(option.id);\n }\n\n // If multiSelect is false or undefined, allow the source to be used multiple times\n return sourceOptions;\n });\n\n // Check if we can add more filters\n const canAddFilter = availableSources.length > 0;\n\n const handleAddFilter = () => {\n if (!canAddFilter) return;\n\n const newFilter = {\n id: createId(),\n source: availableSources[0].id,\n operator: Operator.IS,\n value: rules[availableSources[0].id]?.multiSelect ? [] : '',\n };\n\n const newFilters = [...internalFilters, newFilter];\n setInternalFilters(newFilters);\n\n // Focus the new filter after it's rendered\n requestAnimationFrame(() => {\n filterRefs.current[newFilter.id]?.focus();\n });\n\n // Notify parent if callback provided\n onFilterAdd?.(newFilter);\n };\n\n const handleRemoveFilter = (filter: FilterValue) => {\n const newFilters = internalFilters.filter((f) => f.id !== filter.id);\n setInternalFilters(newFilters);\n onFilterRemove(filter.id);\n };\n\n return (\n <fieldset className={cn('flex flex-wrap gap-2', className)}>\n <legend className=\"sr-only\">Filters</legend>\n {internalFilters?.map((filter) => {\n // For each filter, include available sources plus its own current source\n const filterSourceOptions = sourceOptions.filter(\n (option) =>\n !usedSources.includes(option.id) || // Available sources\n option.id === filter.source, // Or its own current source\n );\n\n return (\n <Filter\n key={filter.id}\n ref={(ref) => {\n filterRefs.current[filter.id] = ref;\n }}\n filter={filter}\n sourceOptions={filterSourceOptions}\n rules={rules}\n onRemoveFilter={() => handleRemoveFilter(filter)}\n onFilterChange={onFilterChange}\n />\n );\n })}\n {canAddFilter && (\n <Button\n onClick={handleAddFilter}\n variant=\"ghost\"\n className=\"border border-foreground rounded-full gap-1\"\n size=\"sm\"\n title=\"Add filter\"\n >\n <Plus size={16} />\n Add filter\n </Button>\n )}\n </fieldset>\n );\n};\n\nexport { Filters };\n"],"names":["Operator","CustomValueInput","inputValue","setInputValue","customValuePlaceholder","customValueButtonLabel","onSubmit","handleKeyDown","inputRef","useRef","React","createElement","className","e","preventDefault","Input","ref","type","value","onChange","target","placeholder","onKeyDown","Button","variant","MultiSelectDropdown","internalFilter","valueOptions","onConfirm","onClose","pendingValues","setPendingValues","useState","getInitialValues","Array","isArray","announcement","setAnnouncement","buttonRefs","applyButtonRef","handleValueToggle","valueId","option","find","opt","id","isCurrentlySelected","includes","prev","newValues","filter","action","label","handleConfirm","isValueSelected","focusButton","index","length","current","focus","key","nextIndex","Math","min","prevIndex","max","role","map","el","onClick","Check","size","disabled","SingleSelectDropdown","onValueSelect","Filter","forwardRef","sourceOptions","rules","onSourceChange","onRemoveFilter","onFilterChange","selectPlaceholder","sourceFirstMessage","setInternalFilter","source","operator","IS","openDropdownType","setOpenDropdownType","sourceButtonRef","divRef","useImperativeHandle","currentRules","operatorOptions","operators","isMultiSelect","multiSelect","inputType","CONTAINS","DOES_NOT_CONTAIN","getValueOptions","getSelectedValues","getDisplayValue","selectedValues","useMultiSelectHelpers","useEffect","timeoutId","setTimeout","clearTimeout","checkAndTriggerChange","newFilter","hasValue","trim","updateFilterField","field","sourceRules","ButtonGroup","orientation","DropdownMenu","open","onOpenChange","modal","DropdownMenuTrigger","asChild","DropdownMenuContent","align","DropdownMenuItem","onSelect","Popover","PopoverTrigger","PopoverContent","isInput","shiftKey","button","document","querySelector","XCircle","weight","displayName","Filters","filters","onFilterRemove","onFilterAdd","internalFilters","setInternalFilters","filterRefs","usedSources","Boolean","availableSources","canAddFilter","cn","filterSourceOptions","newFilters","f","handleRemoveFilter","createId","requestAnimationFrame","title","Plus"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import*as e from"react";import{Check as t,SpinnerGap as o}from"@phosphor-icons/react";import{c as a}from"./utils.C6Qu-kwd.js";import{CommandItem as n}from"../components/ui/command.js";import{C as r}from"./combobox.MyoPH18G.js";const s="brainfish-google-fonts-cache",l="brainfish-google-fonts-cache-timestamp",c=/* @__PURE__ */new Set,i=()=>{try{const e=localStorage.getItem(l);if(!e)return null;if(Date.now()-parseInt(e,10)>864e5)return localStorage.removeItem(s),localStorage.removeItem(l),null;const t=localStorage.getItem(s);return t?JSON.parse(t):null}catch{return null}},m=e=>{try{localStorage.setItem(s,JSON.stringify(e)),localStorage.setItem(l,Date.now().toString())}catch{}},f=e=>{if(c.has(e))return;const t=document.createElement("link");t.href=`https://fonts.googleapis.com/css2?family=${e.replace(/ /g,"+")}&display=swap`,t.rel="stylesheet",document.head.appendChild(t),c.add(e)},u=()=>{try{localStorage.removeItem(s),localStorage.removeItem(l)}catch{}};function d({apiKey:t,maxFonts:o=500,onError:a}){const[n,r]=e.useState([]),[s,l]=e.useState(!0),c=e.useRef(a);return e.useEffect(()=>{c.current=a},[a]),e.useEffect(()=>{(async()=>{const e=i();if(e&&e.length>0)return r(e.slice(0,o)),void l(!1);try{const e=await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${t}&sort=popularity`);if(!e.ok)throw new Error(`Failed to fetch fonts: ${e.statusText}`);const a=(await e.json()).items.map(e=>({value:e.family,label:e.family,category:e.category,variants:e.variants}));m(a),r(a.slice(0,o))}catch(a){const e=a instanceof Error?a:new Error("Failed to fetch fonts");console.error("FontPicker: Failed to fetch Google Fonts",e),c.current?.(e)}finally{l(!1)}})()},[t,o]),{fonts:n,isLoading:s,loadFont:f}}const g=e.memo(function({font:o,isSelected:r,onSelect:s}){const l=e.useRef(null),[c,i]=e.useState(!1);e.useEffect(()=>{const e=new IntersectionObserver(([t])=>{t.isIntersecting&&(i(!0),e.disconnect())},{threshold:.1});return l.current&&e.observe(l.current),()=>e.disconnect()},[]),e.useEffect(()=>{c&&f(o.value)},[c,o.value]);const m=`Select ${o.label} font${o.category?`, ${o.category} category`:""}`;/* @__PURE__ */
1
+ import*as e from"react";import{Check as t,SpinnerGap as o}from"@phosphor-icons/react";import{c as a}from"./utils.C6Qu-kwd.js";import{CommandItem as n}from"../components/ui/command.js";import{C as r}from"./combobox.CLT9CXUV.js";const s="brainfish-google-fonts-cache",l="brainfish-google-fonts-cache-timestamp",c=/* @__PURE__ */new Set,i=()=>{try{const e=localStorage.getItem(l);if(!e)return null;if(Date.now()-parseInt(e,10)>864e5)return localStorage.removeItem(s),localStorage.removeItem(l),null;const t=localStorage.getItem(s);return t?JSON.parse(t):null}catch{return null}},m=e=>{try{localStorage.setItem(s,JSON.stringify(e)),localStorage.setItem(l,Date.now().toString())}catch{}},f=e=>{if(c.has(e))return;const t=document.createElement("link");t.href=`https://fonts.googleapis.com/css2?family=${e.replace(/ /g,"+")}&display=swap`,t.rel="stylesheet",document.head.appendChild(t),c.add(e)},u=()=>{try{localStorage.removeItem(s),localStorage.removeItem(l)}catch{}};function d({apiKey:t,maxFonts:o=500,onError:a}){const[n,r]=e.useState([]),[s,l]=e.useState(!0),c=e.useRef(a);return e.useEffect(()=>{c.current=a},[a]),e.useEffect(()=>{(async()=>{const e=i();if(e&&e.length>0)return r(e.slice(0,o)),void l(!1);try{const e=await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${t}&sort=popularity`);if(!e.ok)throw new Error(`Failed to fetch fonts: ${e.statusText}`);const a=(await e.json()).items.map(e=>({value:e.family,label:e.family,category:e.category,variants:e.variants}));m(a),r(a.slice(0,o))}catch(a){const e=a instanceof Error?a:new Error("Failed to fetch fonts");console.error("FontPicker: Failed to fetch Google Fonts",e),c.current?.(e)}finally{l(!1)}})()},[t,o]),{fonts:n,isLoading:s,loadFont:f}}const g=e.memo(function({font:o,isSelected:r,onSelect:s}){const l=e.useRef(null),[c,i]=e.useState(!1);e.useEffect(()=>{const e=new IntersectionObserver(([t])=>{t.isIntersecting&&(i(!0),e.disconnect())},{threshold:.1});return l.current&&e.observe(l.current),()=>e.disconnect()},[]),e.useEffect(()=>{c&&f(o.value)},[c,o.value]);const m=`Select ${o.label} font${o.category?`, ${o.category} category`:""}`;/* @__PURE__ */
2
2
  return e.createElement(n,{ref:l,value:o.label,onSelect:s,className:"cursor-pointer","aria-label":m,"aria-selected":r},
3
3
  /* @__PURE__ */e.createElement("span",{style:{fontFamily:c?o.value:void 0},className:"flex-1 truncate"},o.label),o.category&&/* @__PURE__ */e.createElement("span",{className:"text-xs text-muted-foreground capitalize"},o.category),
4
4
  /* @__PURE__ */e.createElement(t,{className:a("ml-2 h-4 w-4",r?"opacity-100":"opacity-0")}))});function p({value:t,onChange:a,apiKey:n,placeholder:s="Select a font...",searchPlaceholder:l="Search fonts...",noResultsText:c="No fonts found.",loadingText:i="Loading fonts...",disabled:m=!1,className:f,dropdownWidth:u,maxFonts:p=500,onError:h}){const{fonts:y,isLoading:S,loadFont:E}=d({apiKey:n,maxFonts:p,onError:h});e.useEffect(()=>{t&&E(t)},[t,E]);const v=y;/* @__PURE__ */
5
5
  return e.createElement(r,{options:v,value:t,onChange:a,defaultValueLabel:s,placeholder:l,noResultsLabel:c,disabled:m||S,className:f,dropdownWidth:u,renderTrigger:({selectedItem:t})=>S?/* @__PURE__ */e.createElement("span",{className:"flex items-center gap-2 text-muted-foreground"},/* @__PURE__ */e.createElement(o,{className:"h-4 w-4 animate-spin"}),i):t?/* @__PURE__ */e.createElement("span",{style:{fontFamily:t.value}},t.label):/* @__PURE__ */e.createElement("span",{className:"text-muted-foreground"},s),renderItem:({item:t,isSelected:o,onSelect:a})=>/* @__PURE__ */e.createElement(g,{font:t,isSelected:o,onSelect:a})})}export{p as F,g as a,u as c,i as g,f as l,m as s,d as u};
6
- //# sourceMappingURL=font-picker.B9GPXyK4.js.map
6
+ //# sourceMappingURL=font-picker.Dwwe5av-.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"font-picker.B9GPXyK4.js","sources":["../../../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 { 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\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":["FONT_CACHE_KEY","FONT_CACHE_TIMESTAMP_KEY","loadedFonts","Set","getCachedFonts","timestamp","localStorage","getItem","Date","now","parseInt","removeItem","cached","JSON","parse","setCachedFonts","fonts","setItem","stringify","toString","loadFont","fontFamily","has","link","document","createElement","href","replace","rel","head","appendChild","add","clearFontCache","useFontOptions","apiKey","maxFonts","onError","setFonts","React","useState","isLoading","setIsLoading","onErrorRef","useRef","useEffect","current","async","cachedFonts","length","slice","response","fetch","ok","Error","statusText","fontOptions","json","items","map","fontItem","value","family","label","category","variants","err","error","console","fetchFonts","FontPickerItem","memo","font","isSelected","onSelect","itemRef","isVisible","setIsVisible","observer","IntersectionObserver","entry","isIntersecting","disconnect","threshold","observe","ariaLabel","CommandItem","ref","className","style","Check","cn","FontPicker","onChange","placeholder","searchPlaceholder","noResultsText","loadingText","disabled","dropdownWidth","options","Combobox","defaultValueLabel","noResultsLabel","renderTrigger","selectedItem","SpinnerGap","renderItem","item"],"mappings":"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"}
1
+ {"version":3,"file":"font-picker.Dwwe5av-.js","sources":["../../../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 { 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\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":["FONT_CACHE_KEY","FONT_CACHE_TIMESTAMP_KEY","loadedFonts","Set","getCachedFonts","timestamp","localStorage","getItem","Date","now","parseInt","removeItem","cached","JSON","parse","setCachedFonts","fonts","setItem","stringify","toString","loadFont","fontFamily","has","link","document","createElement","href","replace","rel","head","appendChild","add","clearFontCache","useFontOptions","apiKey","maxFonts","onError","setFonts","React","useState","isLoading","setIsLoading","onErrorRef","useRef","useEffect","current","async","cachedFonts","length","slice","response","fetch","ok","Error","statusText","fontOptions","json","items","map","fontItem","value","family","label","category","variants","err","error","console","fetchFonts","FontPickerItem","memo","font","isSelected","onSelect","itemRef","isVisible","setIsVisible","observer","IntersectionObserver","entry","isIntersecting","disconnect","threshold","observe","ariaLabel","CommandItem","ref","className","style","Check","cn","FontPicker","onChange","placeholder","searchPlaceholder","noResultsText","loadingText","disabled","dropdownWidth","options","Combobox","defaultValueLabel","noResultsLabel","renderTrigger","selectedItem","SpinnerGap","renderItem","item"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import*as e from"react";import{Crown as a,Copy as t,SidebarSimple as n,Lightning as l,Question as r}from"@phosphor-icons/react";import{L as o}from"./logo.DtwFGMBj.js";import{B as i}from"./button.Cs1X8upM.js";import{DropdownMenu as s,DropdownMenuTrigger as c,DropdownMenuContent as m,DropdownMenuSeparator as u,DropdownMenuGroup as d,DropdownMenuSub as p,DropdownMenuSubTrigger as g,DropdownMenuPortal as b,DropdownMenuSubContent as h,DropdownMenuRadioGroup as f,DropdownMenuRadioItem as E,DropdownMenuShortcut as v,DropdownMenuItem as x}from"../components/ui/dropdown-menu.js";import{Avatar as k,AvatarImage as N,AvatarFallback as w,AvatarBadge as y}from"../components/ui/avatar.js";import{c as C}from"./utils.C6Qu-kwd.js";import{u as _}from"./feature-flags.DOcVlPHk.js";import{A}from"./ai-badge.Be0978vU.js";function z({accountPhoto:n,accountName:l,accountDropdownItems:r,teams:o,side:_="bottom",align:A="end",isSuperAdmin:z=!1,...S}){const j=o?.length>1||z,T=j&&o?.find(e=>e.isCurrent)?.name||"",V=l[0],D=Array.isArray(r[0])?r:[r],P=e.useCallback((e,a)=>t=>{t.stopPropagation(),a?a(e):navigator.clipboard.writeText(e)},[]),R=(a,n,l)=>a&&/* @__PURE__ */e.createElement("div",{className:"flex w-full items-center gap-1 text-xs text-subtlest ml-7"},a,null!=n&&/* @__PURE__ */e.createElement(i,{variant:"ghost",size:"icon",className:"size-4 shrink-0 p-0 [&>svg]:!size-3",onClick:P(n,l),"aria-label":"Copy ID"},
2
+ /* @__PURE__ */e.createElement(t,{className:"size-3"})));/* @__PURE__ */
3
+ return e.createElement(s,{...S},/* @__PURE__ */e.createElement(c,{asChild:!0},/* @__PURE__ */e.createElement(i,{size:"default",variant:"ghost",className:"rounded-full p-[1px] border border-dark-300 relative","aria-label":"Toggle account dropdown"},
4
+ /* @__PURE__ */e.createElement(k,null,/* @__PURE__ */e.createElement(N,{src:n,alt:`${l}'s account`,className:C("block rounded-full border border-background",z&&"border-2 border-solid border-red-700 inset-1")}),/* @__PURE__ */e.createElement(w,null,V),z&&/* @__PURE__ */e.createElement(y,{className:"bg-red-700 text-white"},/* @__PURE__ */e.createElement(a,null))),j&&/* @__PURE__ */e.createElement("span",{className:"text-xs text-default font-semibold pr-1"},T))),/* @__PURE__ */e.createElement(m,{side:_,align:A,className:"border-none shadow-[2px_4px_12px_0px_rgba(0,0,0,0.14)] dark:shadow-[2px_4px_12px_0px_rgba(255,255,255,0.14)]"},D.map((a,t)=>/* @__PURE__ */e.createElement(e.Fragment,{key:t},t>0&&/* @__PURE__ */e.createElement(u,null),/* @__PURE__ */e.createElement(d,null,a.map(a=>"submenu"===a.type?/* @__PURE__ */e.createElement(p,{key:a.id},/* @__PURE__ */e.createElement(g,{secondaryRow:R(a.secondaryLabel,a.copyValue,a.onCopy)},a.icon,a.label),/* @__PURE__ */e.createElement(b,null,/* @__PURE__ */e.createElement(h,{className:"border-none shadow-[2px_4px_12px_0px_rgba(0,0,0,0.14)] dark:shadow-[2px_4px_12px_0px_rgba(255,255,255,0.14)]"},null!=a.value&&a.onValueChange?/* @__PURE__ */e.createElement(f,{value:a.value,onValueChange:a.onValueChange},a.items.map(a=>/* @__PURE__ */e.createElement(E,{key:a.id,value:a.value},a.icon,a.label,null!=a.shortcut&&/* @__PURE__ */e.createElement(v,null,a.shortcut)))):a.items.map(a=>/* @__PURE__ */e.createElement(x,{key:a.id,onClick:a.onClick},a.icon,a.label,null!=a.shortcut&&/* @__PURE__ */e.createElement(v,null,a.shortcut)))))):/* @__PURE__ */e.createElement(x,{key:a.id,onClick:a.onClick,secondaryRow:R(a.secondaryLabel,a.copyValue,a.onCopy)},a.icon,a.label,null!=a.shortcut&&/* @__PURE__ */e.createElement(v,null,a.shortcut))))))))}const S=60,j=e.forwardRef(function({className:a,isSideNavOpen:t=!1,onSideNavigationToggle:s,onHelpClick:c,onBrainfishNativeClick:m,accountName:u,accountPhoto:d,accountDropdownItems:p,teams:g,side:b,align:h,showSuggestionAction:f,suggestionAction:E,onSuggestionActionClick:v,onLogoNav:x,isHomePath:k=!1,showSidebarToggle:N=!0,isRightSidebarOpen:w=!1,sidebarToggleRef:y,isSuperAdmin:S,isViewer:j,...T},V){const D=f&&!!E&&!!v,P=_("platform.brainfish-native.enabled",!1),R=!j;/* @__PURE__ */
5
+ return e.createElement("header",{ref:V,...T,className:C("px-4 py-3 relative",a)},/* @__PURE__ */e.createElement("nav",{"aria-label":"Account navigation",className:"flex justify-between items-center"},/* @__PURE__ */e.createElement("div",{className:"flex items-center gap-2"},/* @__PURE__ */e.createElement("a",{href:"/",onClick:e=>{!(0!==e.button||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)&&(e.preventDefault(),x())},"aria-current":k?"page":void 0},
6
+ /* @__PURE__ */e.createElement(o,{variant:"full",color:k?"fill":"outline",width:130,height:20,className:"hidden md:block"}),
7
+ /* @__PURE__ */e.createElement(o,{variant:"mark",color:k?"fill":"outline",width:37,height:20,className:"block md:hidden"})),N&&/* @__PURE__ */e.createElement(i,{ref:y,size:"icon",variant:"ghost","aria-label":"Toggle side navigation",className:"[&_svg]:size-6",onClick:s},
8
+ /* @__PURE__ */e.createElement(n,{"aria-hidden":"true",weight:t?"fill":"regular"}))),/* @__PURE__ */e.createElement("div",{className:"flex items-center gap-4"},D&&/* @__PURE__ */e.createElement(i,{variant:"suggestion",onClick:v},/* @__PURE__ */e.createElement(l,{weight:"fill","aria-hidden":"true"})," ",E),P&&!w&&/* @__PURE__ */e.createElement("button",{type:"button","aria-label":"Open Ask Brainfish",className:"flex items-center gap-2 rounded border border-dark-300 bg-surface py-1 pl-1 pr-1.5 cursor-pointer hover:bg-dark-200 active:bg-dark-400 focus-visible:outline-none",onClick:m},
9
+ /* @__PURE__ */e.createElement(A,{size:24}),
10
+ /* @__PURE__ */e.createElement("span",{className:"text-xs font-normal text-dark-900 whitespace-nowrap"},"Ask Brainfish")),/* @__PURE__ */e.createElement(z,{accountName:u,accountPhoto:d,accountDropdownItems:p,teams:g,side:b,align:h,isSuperAdmin:S}),R&&/* @__PURE__ */e.createElement(i,{size:"icon",variant:"ghost","aria-label":"Open help",className:"[&_svg]:size-6 fixed z-10 bottom-1 left-1",onClick:c},
11
+ /* @__PURE__ */e.createElement(r,{"aria-hidden":"true"})))))});export{S as H,j as a};
12
+ //# sourceMappingURL=header-nav.CtUIc9pI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-nav.CtUIc9pI.js","sources":["../../../src/layouts/header-nav/account-dropdown.tsx","../../../src/layouts/header-nav/header-nav.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Copy, Crown } from '@phosphor-icons/react';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '../../components/ui/dropdown-menu';\nimport { Avatar, AvatarFallback, AvatarImage, AvatarBadge } from '../../components/ui/avatar';\nimport { Button } from '../../components/ui/button';\n\nimport { cn } from '@/lib/utils';\n\nexport type AccountDropdownActionItem = {\n type?: 'action';\n id: string;\n label: string;\n icon?: React.ReactNode;\n onClick: () => void;\n /** Second line (e.g. \"ID: xyz\"); copy button shown when copyValue is set. */\n secondaryLabel?: React.ReactNode;\n copyValue?: string;\n onCopy?: (value: string) => void;\n /** Keyboard shortcut hint (e.g. \"⌘K\") shown on the right. */\n shortcut?: string;\n};\n\nexport type AccountDropdownSubOption = {\n id: string;\n label: string;\n icon?: React.ReactNode;\n /** Required for radio mode. */\n value?: string;\n /** Used in plain (non-radio) mode. */\n onClick?: () => void;\n /** Keyboard shortcut hint (e.g. \"⌘K\") shown on the right. */\n shortcut?: string;\n};\n\n/** Sub-option with a required `value`, used in radio-mode submenus. */\nexport type AccountDropdownRadioSubOption = Omit<AccountDropdownSubOption, 'value' | 'onClick'> & {\n value: string;\n};\n\ntype AccountDropdownSubmenuBase = {\n type: 'submenu';\n id: string;\n label: string;\n icon?: React.ReactNode;\n /** Second line on the submenu trigger (e.g. \"ID: xyz\"); copy button when copyValue is set. */\n secondaryLabel?: React.ReactNode;\n copyValue?: string;\n onCopy?: (value: string) => void;\n};\n\nexport type AccountDropdownSubmenuItem = AccountDropdownSubmenuBase &\n (\n | {\n /** The currently selected value. When provided with onValueChange, renders items as a radio group with selection indicators. */\n value: string;\n onValueChange: (value: string) => void;\n items: AccountDropdownRadioSubOption[];\n }\n | {\n value?: never;\n onValueChange?: never;\n items: AccountDropdownSubOption[];\n }\n );\n\nexport type AccountDropdownItem = AccountDropdownActionItem | AccountDropdownSubmenuItem;\n\nexport type AccountDropdownTeam = {\n id: string;\n name: string;\n isCurrent: boolean;\n logoUrl?: string;\n};\n\nexport interface AccountDropdownProps extends React.ComponentPropsWithoutRef<typeof DropdownMenu> {\n accountPhoto: string;\n accountName: string;\n /** Pass a flat array for a single group, or an array of arrays for multiple groups separated by dividers. */\n accountDropdownItems: AccountDropdownItem[] | AccountDropdownItem[][];\n teams: AccountDropdownTeam[];\n /** Side the dropdown opens on relative to the trigger. Defaults to 'bottom'. */\n side?: React.ComponentPropsWithoutRef<typeof DropdownMenuContent>['side'];\n /** Alignment of the dropdown relative to the trigger. Defaults to 'end'. */\n align?: React.ComponentPropsWithoutRef<typeof DropdownMenuContent>['align'];\n isSuperAdmin?: boolean;\n}\n\nexport function AccountDropdown({\n accountPhoto,\n accountName,\n accountDropdownItems,\n teams,\n side = 'bottom',\n align = 'end',\n isSuperAdmin = false,\n ...props\n}: AccountDropdownProps) {\n const showTeamName = teams?.length > 1 || isSuperAdmin;\n const currentTeamName = (showTeamName && teams?.find((el) => el.isCurrent)?.name) || '';\n const initial = accountName[0];\n\n const groups: AccountDropdownItem[][] = Array.isArray(accountDropdownItems[0])\n ? (accountDropdownItems as AccountDropdownItem[][])\n : [accountDropdownItems as AccountDropdownItem[]];\n\n const handleCopy = React.useCallback((value: string, onCopy?: (value: string) => void) => {\n return (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onCopy) onCopy(value);\n else navigator.clipboard.writeText(value);\n };\n }, []);\n\n const renderCopyRow = (\n secondaryLabel: React.ReactNode,\n copyValue: string | undefined,\n onCopy?: (value: string) => void,\n ) =>\n secondaryLabel && (\n <div className=\"flex w-full items-center gap-1 text-xs text-subtlest ml-7\">\n {secondaryLabel}\n {copyValue != null && (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-4 shrink-0 p-0 [&>svg]:!size-3\"\n onClick={handleCopy(copyValue, onCopy)}\n aria-label=\"Copy ID\"\n >\n <Copy className=\"size-3\" />\n </Button>\n )}\n </div>\n );\n\n return (\n <DropdownMenu {...props}>\n <DropdownMenuTrigger asChild>\n <Button\n size=\"default\"\n variant=\"ghost\"\n className=\"rounded-full p-[1px] border border-dark-300 relative\"\n aria-label=\"Toggle account dropdown\"\n >\n <Avatar>\n <AvatarImage\n src={accountPhoto}\n alt={`${accountName}'s account`}\n className={cn(\n 'block rounded-full border border-background',\n isSuperAdmin && 'border-2 border-solid border-red-700 inset-1',\n )}\n />\n <AvatarFallback>{initial}</AvatarFallback>\n {isSuperAdmin && (\n <AvatarBadge className=\"bg-red-700 text-white\">\n <Crown />\n </AvatarBadge>\n )}\n </Avatar>\n {showTeamName && <span className=\"text-xs text-default font-semibold pr-1\">{currentTeamName}</span>}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side={side}\n align={align}\n className=\"border-none shadow-[2px_4px_12px_0px_rgba(0,0,0,0.14)] dark:shadow-[2px_4px_12px_0px_rgba(255,255,255,0.14)]\"\n >\n {groups.map((group, groupIndex) => (\n <React.Fragment key={groupIndex}>\n {groupIndex > 0 && <DropdownMenuSeparator />}\n <DropdownMenuGroup>\n {group.map((item) =>\n item.type === 'submenu' ? (\n <DropdownMenuSub key={item.id}>\n <DropdownMenuSubTrigger\n secondaryRow={renderCopyRow(item.secondaryLabel, item.copyValue, item.onCopy)}\n >\n {item.icon}\n {item.label}\n </DropdownMenuSubTrigger>\n <DropdownMenuPortal>\n <DropdownMenuSubContent className=\"border-none shadow-[2px_4px_12px_0px_rgba(0,0,0,0.14)] dark:shadow-[2px_4px_12px_0px_rgba(255,255,255,0.14)]\">\n {item.value != null && item.onValueChange ? (\n <DropdownMenuRadioGroup value={item.value} onValueChange={item.onValueChange}>\n {item.items.map((option) => (\n <DropdownMenuRadioItem key={option.id} value={option.value}>\n {option.icon}\n {option.label}\n {option.shortcut != null && (\n <DropdownMenuShortcut>{option.shortcut}</DropdownMenuShortcut>\n )}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n ) : (\n item.items.map((option) => (\n <DropdownMenuItem key={option.id} onClick={option.onClick}>\n {option.icon}\n {option.label}\n {option.shortcut != null && (\n <DropdownMenuShortcut>{option.shortcut}</DropdownMenuShortcut>\n )}\n </DropdownMenuItem>\n ))\n )}\n </DropdownMenuSubContent>\n </DropdownMenuPortal>\n </DropdownMenuSub>\n ) : (\n <DropdownMenuItem\n key={item.id}\n onClick={item.onClick}\n secondaryRow={renderCopyRow(item.secondaryLabel, item.copyValue, item.onCopy)}\n >\n {item.icon}\n {item.label}\n {item.shortcut != null && <DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut>}\n </DropdownMenuItem>\n ),\n )}\n </DropdownMenuGroup>\n </React.Fragment>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n","import * as React from 'react';\nimport { Question, SidebarSimple, Lightning } from '@phosphor-icons/react';\n\nimport { Logo } from '../../components/logo';\nimport { AIBadge } from '../../components/ai-button';\nimport { Button } from '../../components/ui/button';\nimport { AccountDropdown, AccountDropdownProps } from './account-dropdown';\n\nimport { cn } from '@/lib/utils';\nimport { useBooleanFlagValue } from '@/lib/feature-flags';\n\ninterface HeaderNavProps extends React.ComponentPropsWithoutRef<'header'>, Omit<AccountDropdownProps, 'dir'> {\n isSideNavOpen?: boolean;\n onSideNavigationToggle: () => void;\n onHelpClick: () => void;\n onBrainfishNativeClick: () => void;\n onLogoNav: () => void;\n isHomePath?: boolean;\n showSuggestionAction?: boolean;\n suggestionAction?: React.ReactNode;\n onSuggestionActionClick?: () => void;\n showSidebarToggle?: boolean;\n isRightSidebarOpen?: boolean;\n isSuperAdmin?: boolean;\n isViewer?: boolean;\n /** Ref for the sidebar toggle button; used by layout to move focus here when sidebar is collapsed. */\n sidebarToggleRef?: React.Ref<HTMLButtonElement>;\n}\n\nexport const HEADER_NAV_HEIGHT = 60;\n\nexport const HeaderNav = React.forwardRef<HTMLElement, HeaderNavProps>(function HeaderNav(\n {\n className,\n isSideNavOpen = false,\n onSideNavigationToggle,\n onHelpClick,\n onBrainfishNativeClick,\n accountName,\n accountPhoto,\n accountDropdownItems,\n teams,\n side,\n align,\n showSuggestionAction,\n suggestionAction,\n onSuggestionActionClick,\n onLogoNav,\n isHomePath = false,\n showSidebarToggle = true,\n isRightSidebarOpen = false,\n sidebarToggleRef,\n isSuperAdmin,\n isViewer,\n ...props\n },\n ref,\n) {\n const hasSuggestionAction = showSuggestionAction && !!suggestionAction && !!onSuggestionActionClick;\n const brainfishNativeEnabled = useBooleanFlagValue('platform.brainfish-native.enabled', false);\n const isHelpButtonEnabled = !isViewer;\n\n return (\n <header ref={ref} {...props} className={cn('px-4 py-3 relative', className)}>\n <nav aria-label=\"Account navigation\" className=\"flex justify-between items-center\">\n <div className=\"flex items-center gap-2\">\n <a\n href=\"/\"\n onClick={(e) => {\n const isPlainLeftClick = e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;\n\n if (isPlainLeftClick) {\n e.preventDefault();\n onLogoNav();\n }\n }}\n aria-current={isHomePath ? 'page' : undefined}\n >\n <Logo\n variant=\"full\"\n color={isHomePath ? 'fill' : 'outline'}\n width={130}\n height={20}\n className=\"hidden md:block\"\n />\n <Logo\n variant=\"mark\"\n color={isHomePath ? 'fill' : 'outline'}\n width={37}\n height={20}\n className=\"block md:hidden\"\n />\n </a>\n {showSidebarToggle && (\n <Button\n ref={sidebarToggleRef}\n size=\"icon\"\n variant=\"ghost\"\n aria-label=\"Toggle side navigation\"\n className=\"[&_svg]:size-6\"\n onClick={onSideNavigationToggle}\n >\n <SidebarSimple aria-hidden=\"true\" weight={isSideNavOpen ? 'fill' : 'regular'} />\n </Button>\n )}\n </div>\n <div className=\"flex items-center gap-4\">\n {hasSuggestionAction && (\n <Button variant=\"suggestion\" onClick={onSuggestionActionClick}>\n <Lightning weight=\"fill\" aria-hidden=\"true\" /> {suggestionAction}\n </Button>\n )}\n {brainfishNativeEnabled && !isRightSidebarOpen && (\n <button\n type=\"button\"\n aria-label=\"Open Ask Brainfish\"\n className=\"flex items-center gap-2 rounded border border-dark-300 bg-surface py-1 pl-1 pr-1.5 cursor-pointer hover:bg-dark-200 active:bg-dark-400 focus-visible:outline-none\"\n onClick={onBrainfishNativeClick}\n >\n <AIBadge size={24} />\n <span className=\"text-xs font-normal text-dark-900 whitespace-nowrap\">Ask Brainfish</span>\n </button>\n )}\n <AccountDropdown\n accountName={accountName}\n accountPhoto={accountPhoto}\n accountDropdownItems={accountDropdownItems}\n teams={teams}\n side={side}\n align={align}\n isSuperAdmin={isSuperAdmin}\n />\n {isHelpButtonEnabled && (\n <Button\n size=\"icon\"\n variant=\"ghost\"\n aria-label=\"Open help\"\n className=\"[&_svg]:size-6 fixed z-10 bottom-1 left-1\"\n onClick={onHelpClick}\n >\n <Question aria-hidden=\"true\" />\n </Button>\n )}\n </div>\n </nav>\n </header>\n );\n});\n"],"names":["AccountDropdown","accountPhoto","accountName","accountDropdownItems","teams","side","align","isSuperAdmin","props","showTeamName","length","currentTeamName","find","el","isCurrent","name","initial","groups","Array","isArray","handleCopy","React","useCallback","value","onCopy","e","stopPropagation","navigator","clipboard","writeText","renderCopyRow","secondaryLabel","copyValue","createElement","className","Button","variant","size","onClick","Copy","DropdownMenu","DropdownMenuTrigger","asChild","Avatar","AvatarImage","src","alt","cn","AvatarFallback","AvatarBadge","Crown","DropdownMenuContent","map","group","groupIndex","Fragment","key","DropdownMenuSeparator","DropdownMenuGroup","item","type","DropdownMenuSub","id","DropdownMenuSubTrigger","secondaryRow","icon","label","DropdownMenuPortal","DropdownMenuSubContent","onValueChange","DropdownMenuRadioGroup","items","option","DropdownMenuRadioItem","shortcut","DropdownMenuShortcut","DropdownMenuItem","HEADER_NAV_HEIGHT","HeaderNav","forwardRef","isSideNavOpen","onSideNavigationToggle","onHelpClick","onBrainfishNativeClick","showSuggestionAction","suggestionAction","onSuggestionActionClick","onLogoNav","isHomePath","showSidebarToggle","isRightSidebarOpen","sidebarToggleRef","isViewer","ref","hasSuggestionAction","brainfishNativeEnabled","useBooleanFlagValue","isHelpButtonEnabled","href","button","metaKey","ctrlKey","shiftKey","altKey","preventDefault","Logo","color","width","height","SidebarSimple","weight","Lightning","AIBadge","Question"],"mappings":"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"}
@@ -1,15 +1,15 @@
1
- import e,{useContext as a,useState as t,useMemo as r,useRef as n,useEffect as l,useCallback as i,isValidElement as o}from"react";import{useMediaQuery as s}from"usehooks-ts";import{a as d,H as c}from"./header-nav.DmlIUqml.js";import{cva as u}from"class-variance-authority";import{c as m}from"./utils.C6Qu-kwd.js";import{ScrollArea as h}from"../components/ui/scroll-area.js";import{F as f}from"./feature-flags.DOcVlPHk.js";import{Item as v}from"../components/ui/item.js";import{S as g}from"./sidebar.CcFsNjrN.js";const b=e.createContext(null),p=e.createContext(null);function w({value:a,children:t}){/* @__PURE__ */
1
+ import e,{useContext as a,useState as t,useMemo as r,useRef as n,useEffect as l,useCallback as i,isValidElement as o}from"react";import{useMediaQuery as s}from"usehooks-ts";import{a as d,H as c}from"./header-nav.CtUIc9pI.js";import{cva as u}from"class-variance-authority";import{c as m}from"./utils.C6Qu-kwd.js";import{ScrollArea as h}from"../components/ui/scroll-area.js";import{F as f}from"./feature-flags.DOcVlPHk.js";import{Item as v}from"../components/ui/item.js";import{S as g}from"./sidebar.BhcrMAU3.js";const b=e.createContext(null),p=e.createContext(null);function w({value:a,children:t}){/* @__PURE__ */
2
2
  return e.createElement(p.Provider,{value:a},t)}function y(){return e.useContext(p)}function N(){const a=e.useContext(p);if(!a)throw new Error("useRightSidebar must be used within a <FullLayout> component.");return{isOpen:a.isRightSidebarOpen,open:a.openRightSidebar,close:a.closeRightSidebar,toggle:a.toggleRightSidebar}}function E({children:a}){/* @__PURE__ */
3
3
  return e.createElement(e.Fragment,null,a)}function S({children:a}){/* @__PURE__ */
4
4
  return e.createElement(e.Fragment,null,a)}function x(a){const{onBrainfishNativeClick:t,...r}=a,n=y();if(!n)return null;/* @__PURE__ */
5
- return e.createElement(d,{...r,showSidebarToggle:!n.disableLeftSideNav,isSideNavOpen:n.isSideNavOpen,onSideNavigationToggle:n.toggleSidebar,onBrainfishNativeClick:()=>{n.toggleRightSidebar(),t?.()},sidebarToggleRef:n.sidebarToggleRef})}E.displayName="FullLayout.MainArea",S.displayName="FullLayout.Body",x.displayName="FullLayout.Header";const R=e.forwardRef(({open:a,widthPx:t,onResizeStart:r,onResizeKeyDown:n,ariaValuenow:l,ariaValuemin:i,ariaValuemax:o,sidebarProps:s,className:d,...c},u)=>/* @__PURE__ */e.createElement("div",{ref:u,id:"left-sidebar-pane","data-slot":"full-layout-left-sidebar",tabIndex:-1,"data-state":a?"open":"closed",className:m("min-w-0 overflow-hidden flex flex-col relative transition-[width] data-[state=closed]:w-0 data-[state=closed]:pointer-events-none data-[state=open]:w-full md:data-[state=open]:w-[var(--left-nav-width)]",d),style:{"--left-nav-width":`${t}px`},...c},
5
+ return e.createElement(d,{...r,showSidebarToggle:!n.disableLeftSideNav,isSideNavOpen:n.isSideNavOpen,isRightSidebarOpen:n.isRightSidebarOpen,onSideNavigationToggle:n.toggleSidebar,onBrainfishNativeClick:()=>{n.toggleRightSidebar(),t?.()},sidebarToggleRef:n.sidebarToggleRef})}E.displayName="FullLayout.MainArea",S.displayName="FullLayout.Body",x.displayName="FullLayout.Header";const R=e.forwardRef(({open:a,widthPx:t,onResizeStart:r,onResizeKeyDown:n,ariaValuenow:l,ariaValuemin:i,ariaValuemax:o,sidebarProps:s,className:d,...c},u)=>/* @__PURE__ */e.createElement("div",{ref:u,id:"left-sidebar-pane","data-slot":"full-layout-left-sidebar",tabIndex:-1,"data-state":a?"open":"closed",className:m("min-w-0 overflow-hidden flex flex-col relative transition-[width] data-[state=closed]:w-0 data-[state=closed]:pointer-events-none data-[state=open]:w-full md:data-[state=open]:w-[var(--left-nav-width)]",d),style:{"--left-nav-width":`${t}px`},...c},
6
6
  /* @__PURE__ */e.createElement(g,{...s,collapsed:!a,className:"transition-[width] w-[var(--left-nav-width)] h-[calc(100dvh-var(--header-nav-height,0))]"}),
7
7
  /* @__PURE__ */e.createElement("div",{role:"separator","aria-orientation":"vertical","aria-valuenow":l,"aria-valuemin":i,"aria-valuemax":o,"aria-label":"Resize left sidebar","aria-controls":"left-sidebar-pane","data-slot":"full-layout-left-sidebar-resize-handle",tabIndex:0,onMouseDown:r,onKeyDown:n,className:"hidden lg:block absolute top-0 right-0 bottom-0 w-1 bg-transparent hover:bg-blue-500 cursor-ew-resize shrink-0 [&>div]:pointer-events-none"},
8
8
  /* @__PURE__ */e.createElement("div",{className:"absolute inset-y-0 -right-1 w-3"}))));function L(t){const r=y(),n=a(b);if(!r)return null;if(r.disableLeftSideNav)return null;const l=null!=n?{...t,appNavActiveId:n.appNavActiveId??t.appNavActiveId,sectionNavActiveId:n.sectionNavActiveId??t.sectionNavActiveId,activeArticleId:n.activeArticleId??t.activeArticleId}:t;/* @__PURE__ */
9
9
  return e.createElement(R,{ref:r.sideNavRef,open:r.isSideNavOpen,widthPx:r.leftNavWidth,onResizeStart:r.handleResizeStart,onResizeKeyDown:r.handleResizeKeyDown,ariaValuenow:r.ariaValuenow,ariaValuemin:r.ariaValuemin,ariaValuemax:r.ariaValuemax,sidebarProps:l})}function M({children:a,className:t}){const r=y(),n=r?.disableMainScrollArea??!1;/* @__PURE__ */
10
10
  return e.createElement(v,{className:m("p-0",n&&"h-[calc(100dvh-var(--header-nav-height))] md:h-[calc(100dvh-var(--header-nav-height)-1rem)] min-h-0 overflow-y-auto p-4 md:p-8",t)},a)}R.displayName="FullLayoutLeftSidebar",L.displayName="FullLayout.LeftSidebar",M.displayName="FullLayout.Main";const k=e.forwardRef(({open:a,scrollable:t=!0,className:r,children:n,...l},i)=>/* @__PURE__ */e.createElement("aside",{ref:i,"data-slot":"full-layout-right-sidebar","data-state":a?"open":"closed",className:m("bg-background shadow-lg flex flex-col","fixed h-[calc(100dvh-var(--header-nav-height))] z-10 w-screen transition-transform duration-300 ease-in-out data-[state=closed]:translate-x-full data-[state=open]:translate-x-0","md:relative md:h-dvh md:w-96 md:top-0","border-dark-300 border-l border-r-0 border-t-0 border-b-0",r),...l},
11
- /* @__PURE__ */e.createElement("div",{className:"flex flex-col flex-1 min-h-0 overflow-hidden","data-slot":"full-layout-right-sidebar-content"},t?/* @__PURE__ */e.createElement(h,{className:"h-[calc(100dvh-var(--header-nav-height))] md:h-dvh"},n):n)));function A({children:a,scrollable:t}){const r=y();return r?/* @__PURE__ */e.createElement(k,{ref:r.rightSidebarRef,open:r.isRightSidebarOpen,scrollable:t,className:"top-[var(--header-nav-height)]"},a):null}k.displayName="FullLayoutRightSidebar",A.displayName="FullLayout.RightSidebar";const z=u("blurry-blob",{variants:{variant:{topLeft:"",bottomRight:"!top-auto !left-auto !-bottom-[10%] !right-0 rotate-180"}},defaultVariants:{variant:"topLeft"}}),F=()=>/* @__PURE__ */e.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"fixed top-0 left-0 w-0 h-0"},/* @__PURE__ */e.createElement("defs",null,/* @__PURE__ */e.createElement("filter",{id:"goo"},/* @__PURE__ */e.createElement("feGaussianBlur",{in:"SourceGraphic",stdDeviation:"10",result:"blur"}),/* @__PURE__ */e.createElement("feColorMatrix",{in:"blur",mode:"matrix",values:"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -8",result:"goo"}),/* @__PURE__ */e.createElement("feBlend",{in:"SourceGraphic",in2:"goo"})))),C=({className:a,variant:t})=>/* @__PURE__ */e.createElement("div",{className:m(z({variant:t}),a)},/* @__PURE__ */e.createElement("div",{className:"gradients-container"},/* @__PURE__ */e.createElement("div",{className:"g1"}),/* @__PURE__ */e.createElement("div",{className:"g2"}),/* @__PURE__ */e.createElement("div",{className:"g3"}),/* @__PURE__ */e.createElement("div",{className:"g4"}))),O=600;function V(a,t){const r={},n=e.Children.toArray(a);for(const e of Object.keys(t)){const a=n.find(a=>o(a)&&a.type===t[e]);r[e]=a??null}return r}const I=e.memo(function({children:a,disableLeftSideNav:d=!1,disableMainScrollArea:u=!1,className:f,forceRightSidebarOpen:v=!1}){const[g,b]=t(()=>"undefined"!=typeof window&&window.innerWidth>767),[p,y]=t(v),[N,R]=t(!1),[k,z]=t(()=>"undefined"!=typeof window?window.innerWidth:0),I=r(()=>k>0?Math.max(260,Math.min(O,Math.round(.18*k))):260,[k]),[j,B]=t(I),D=n(null),T=n(null),_=n(null),H=n(null),W=n(g),K=n(null),P=s("(max-width: 767px)"),$=s("(min-width: 1280px)");l(()=>(document.body.querySelector(".sbdocs")||(document.body.style.overflow="hidden"),()=>{document.body.style.overflow=""}),[]),l(()=>{const e=H.current;if(!e)return;const a=new ResizeObserver(([e])=>{z(e.contentRect.width)});return a.observe(e),()=>a.disconnect()},[]);const G=i(e=>{$||("left"===e?y(!1):b(!1))},[$]),q=i(()=>{G("left"),b(!0),B(I),D.current?.focus()},[G,I]),X=i(()=>{G("left"),b(e=>!e)},[G]),J=i(()=>{G("right"),y(!0)},[G]),Q=i(()=>{y(!1)},[]),U=i(()=>{v||(G("right"),y(e=>!e))},[G,v]),Y=i(e=>e<228?(B(I),b(!1),!0):(B(Math.min(O,e)),!1),[I]),Z=i(e=>{e.preventDefault(),K.current=new AbortController,R(!0)},[]),ee=i(()=>{R(!1),document.body.style.cursor="",document.body.style.userSelect=""},[]),ae=i(e=>{N&&Y(e.clientX)&&ee()},[N,Y,ee]),te=i(()=>ee(),[ee]),re=i((e,a)=>{e&&(a?e.removeAttribute("inert"):e.setAttribute("inert",""))},[]);l(()=>{re(D.current,g),re(T.current,p)},[g,p]),l(()=>{if(!N||!K.current)return;const{signal:e}=K.current;return document.body.style.cursor="ew-resize",document.body.style.userSelect="none",document.addEventListener("mousemove",ae,{signal:e}),document.addEventListener("mouseup",te,{signal:e}),window.addEventListener("mouseup",te,{signal:e}),re(D.current,!1),()=>{K.current?.abort(),K.current=null,document.body.style.cursor="",document.body.style.userSelect="",re(D.current,!0)}},[N]);const ne=i(e=>{"ArrowLeft"===e.key?(e.preventDefault(),Y(j-16)):"ArrowRight"===e.key&&(e.preventDefault(),Y(Math.min(O,j+16)))},[j,Y]),le=d?0:g?P?k:j:0,ie=P?0:p?384:0;l(()=>{b(!P)},[P]),l(()=>{W.current&&!g&&_.current?.focus(),W.current=g},[g]);const oe=O-I,se=0===oe?100:Math.round((j-I)/oe*100),de=r(()=>({isSideNavOpen:g,isRightSidebarOpen:p,toggleSidebar:X,openRightSidebar:J,closeRightSidebar:Q,toggleRightSidebar:U,showSidenav:q,disableLeftSideNav:d??!1,leftNavWidth:j,isMobile:P,sideNavRef:D,rightSidebarRef:T,sidebarToggleRef:_,handleResizeStart:Z,handleResizeKeyDown:ne,ariaValuenow:se,ariaValuemin:0,ariaValuemax:100,disableMainScrollArea:u}),[g,p,X,J,Q,U,q,j,P,d,u,Z,ne,se]),{MainArea:ce,RightSidebar:ue}=V(a,{MainArea:E,RightSidebar:A}),me=o(ce)?ce.props.children:null,{Header:he,Body:fe}=V(me??null,{Header:x,Body:S}),ve=o(fe)?fe.props.children:null,{LeftSidebar:ge,Main:be}=V(ve??null,{LeftSidebar:L,Main:M}),pe=null!=ue,we="md:shadow-[2px_4px_12px_0_rgba(0,0,0,0.14)] md:dark:shadow-[2px_4px_12px_0_rgba(255,255,255,0.14)] md:rounded-lg";/* @__PURE__ */
12
- return e.createElement(w,{value:de},/* @__PURE__ */e.createElement(F,null),/* @__PURE__ */e.createElement(C,{variant:"topLeft"}),/* @__PURE__ */e.createElement(C,{variant:"bottomRight"}),/* @__PURE__ */e.createElement("div",{className:m("min-h-screen w-screen bg-background grid transition-[grid-template-columns] duration-300 ease-in-out",f),style:{gridTemplateColumns:pe?"minmax(0, 1fr) var(--right-col)":"minmax(0, 1fr)","--right-col":pe?`${ie}px`:"0px","--header-nav-height":`${c}px`}},
11
+ /* @__PURE__ */e.createElement("div",{className:"flex flex-col flex-1 min-h-0 overflow-hidden","data-slot":"full-layout-right-sidebar-content"},t?/* @__PURE__ */e.createElement(h,{className:"h-[calc(100dvh-var(--header-nav-height))] md:h-dvh"},n):n)));function A({children:a,scrollable:t}){const r=y();return r?/* @__PURE__ */e.createElement(k,{ref:r.rightSidebarRef,open:r.isRightSidebarOpen,scrollable:t,className:"top-[var(--header-nav-height)]"},a):null}k.displayName="FullLayoutRightSidebar",A.displayName="FullLayout.RightSidebar";const z=u("blurry-blob",{variants:{variant:{topLeft:"",bottomRight:"!top-auto !left-auto !-bottom-[10%] !right-0 rotate-180"}},defaultVariants:{variant:"topLeft"}}),F=()=>/* @__PURE__ */e.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"fixed top-0 left-0 w-0 h-0"},/* @__PURE__ */e.createElement("defs",null,/* @__PURE__ */e.createElement("filter",{id:"goo"},/* @__PURE__ */e.createElement("feGaussianBlur",{in:"SourceGraphic",stdDeviation:"10",result:"blur"}),/* @__PURE__ */e.createElement("feColorMatrix",{in:"blur",mode:"matrix",values:"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -8",result:"goo"}),/* @__PURE__ */e.createElement("feBlend",{in:"SourceGraphic",in2:"goo"})))),O=({className:a,variant:t})=>/* @__PURE__ */e.createElement("div",{className:m(z({variant:t}),a)},/* @__PURE__ */e.createElement("div",{className:"gradients-container"},/* @__PURE__ */e.createElement("div",{className:"g1"}),/* @__PURE__ */e.createElement("div",{className:"g2"}),/* @__PURE__ */e.createElement("div",{className:"g3"}),/* @__PURE__ */e.createElement("div",{className:"g4"}))),C=600;function V(a,t){const r={},n=e.Children.toArray(a);for(const e of Object.keys(t)){const a=n.find(a=>o(a)&&a.type===t[e]);r[e]=a??null}return r}const I=e.memo(function({children:a,disableLeftSideNav:d=!1,disableMainScrollArea:u=!1,className:f,forceRightSidebarOpen:v=!1}){const[g,b]=t(()=>"undefined"!=typeof window&&window.innerWidth>767),[p,y]=t(v),[N,R]=t(!1),[k,z]=t(()=>"undefined"!=typeof window?window.innerWidth:0),I=r(()=>k>0?Math.max(260,Math.min(C,Math.round(.18*k))):260,[k]),[j,B]=t(I),D=n(null),T=n(null),_=n(null),H=n(null),W=n(g),K=n(null),P=s("(max-width: 767px)"),$=s("(min-width: 1280px)");l(()=>(document.body.querySelector(".sbdocs")||(document.body.style.overflow="hidden"),()=>{document.body.style.overflow=""}),[]),l(()=>{const e=H.current;if(!e)return;const a=new ResizeObserver(([e])=>{z(e.contentRect.width)});return a.observe(e),()=>a.disconnect()},[]);const G=i(e=>{$||("left"===e?y(!1):b(!1))},[$]),q=i(()=>{G("left"),b(!0),B(I),D.current?.focus()},[G,I]),X=i(()=>{G("left"),b(e=>!e)},[G]),J=i(()=>{G("right"),y(!0)},[G]),Q=i(()=>{y(!1)},[]),U=i(()=>{v||(G("right"),y(e=>!e))},[G,v]),Y=i(e=>e<228?(B(I),b(!1),!0):(B(Math.min(C,e)),!1),[I]),Z=i(e=>{e.preventDefault(),K.current=new AbortController,R(!0)},[]),ee=i(()=>{R(!1),document.body.style.cursor="",document.body.style.userSelect=""},[]),ae=i(e=>{N&&Y(e.clientX)&&ee()},[N,Y,ee]),te=i(()=>ee(),[ee]),re=i((e,a)=>{e&&(a?e.removeAttribute("inert"):e.setAttribute("inert",""))},[]);l(()=>{re(D.current,g),re(T.current,p)},[g,p]),l(()=>{if(!N||!K.current)return;const{signal:e}=K.current;return document.body.style.cursor="ew-resize",document.body.style.userSelect="none",document.addEventListener("mousemove",ae,{signal:e}),document.addEventListener("mouseup",te,{signal:e}),window.addEventListener("mouseup",te,{signal:e}),re(D.current,!1),()=>{K.current?.abort(),K.current=null,document.body.style.cursor="",document.body.style.userSelect="",re(D.current,!0)}},[N]);const ne=i(e=>{"ArrowLeft"===e.key?(e.preventDefault(),Y(j-16)):"ArrowRight"===e.key&&(e.preventDefault(),Y(Math.min(C,j+16)))},[j,Y]),le=d?0:g?P?k:j:0,ie=P?0:p?384:0;l(()=>{b(!P)},[P]),l(()=>{W.current&&!g&&_.current?.focus(),W.current=g},[g]);const oe=C-I,se=0===oe?100:Math.round((j-I)/oe*100),de=r(()=>({isSideNavOpen:g,isRightSidebarOpen:p,toggleSidebar:X,openRightSidebar:J,closeRightSidebar:Q,toggleRightSidebar:U,showSidenav:q,disableLeftSideNav:d??!1,leftNavWidth:j,isMobile:P,sideNavRef:D,rightSidebarRef:T,sidebarToggleRef:_,handleResizeStart:Z,handleResizeKeyDown:ne,ariaValuenow:se,ariaValuemin:0,ariaValuemax:100,disableMainScrollArea:u}),[g,p,X,J,Q,U,q,j,P,d,u,Z,ne,se]),{MainArea:ce,RightSidebar:ue}=V(a,{MainArea:E,RightSidebar:A}),me=o(ce)?ce.props.children:null,{Header:he,Body:fe}=V(me??null,{Header:x,Body:S}),ve=o(fe)?fe.props.children:null,{LeftSidebar:ge,Main:be}=V(ve??null,{LeftSidebar:L,Main:M}),pe=null!=ue,we="md:shadow-[2px_4px_12px_0_rgba(0,0,0,0.14)] md:dark:shadow-[2px_4px_12px_0_rgba(255,255,255,0.14)] md:rounded-lg";/* @__PURE__ */
12
+ return e.createElement(w,{value:de},/* @__PURE__ */e.createElement(F,null),/* @__PURE__ */e.createElement(O,{variant:"topLeft"}),/* @__PURE__ */e.createElement(O,{variant:"bottomRight"}),/* @__PURE__ */e.createElement("div",{className:m("min-h-screen w-screen bg-background grid transition-[grid-template-columns] duration-300 ease-in-out",f),style:{gridTemplateColumns:pe?"minmax(0, 1fr) var(--right-col)":"minmax(0, 1fr)","--right-col":pe?`${ie}px`:"0px","--header-nav-height":`${c}px`}},
13
13
  /* @__PURE__ */e.createElement("div",{className:"min-w-0 grid grid-rows-[auto_1fr]"},he,/* @__PURE__ */e.createElement("div",{ref:H,"data-sidenav-state":g?"open":"closed","data-right-sidebar-state":p?"open":"closed",className:m("group/sidenav group/rightsidebar min-h-0 min-w-0 grid transition-[grid-template-columns] ease-linear"),style:{gridTemplateColumns:"var(--left-col) minmax(0, 1fr)","--left-col":`${le}px`}},d?/* @__PURE__ */e.createElement("div",{className:"w-0"}):ge,
14
14
  /* @__PURE__ */e.createElement("div",{className:m("relative md:mr-4",{"md:mx-4":!g||d})},u?/* @__PURE__ */e.createElement("div",{className:m("bg-surface",we)},/* @__PURE__ */e.createElement("main",{className:"min-w-0 relative"},be)):/* @__PURE__ */e.createElement(e.Fragment,null,/* @__PURE__ */e.createElement(h,{className:m("bg-surface h-[calc(100dvh-var(--header-nav-height))] md:h-[calc(100dvh-var(--header-nav-height)-1rem)]",we)},
15
15
  /* @__PURE__ */e.createElement("div",{className:"min-h-full"},/* @__PURE__ */e.createElement("main",{className:m("min-w-0 overflow-visible relative p-4 md:p-8")},be))),/* @__PURE__ */e.createElement("div",{"aria-hidden":!0,className:"main-scroll-fade pointer-events-none sticky bottom-4 left-0 right-0 h-16 bg-gradient-to-t from-surface to-transparent md:rounded-b-lg"}))))),pe?ue:null))},function(e,a){return e.children===a.children&&e.className===a.className&&e.disableLeftSideNav===a.disableLeftSideNav&&e.disableMainScrollArea===a.disableMainScrollArea});const j=Object.assign(function(a){const{featureFlags:t,...r}=a;/* @__PURE__ */
@@ -17,4 +17,4 @@ return e.createElement(f,{flags:t},/* @__PURE__ */e.createElement(I,{...r}))},{M
17
17
  /* @__PURE__ */e.createElement("svg",{className:"size-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},/* @__PURE__ */e.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 6h16M4 12h16M4 18h16"}))),
18
18
  /* @__PURE__ */e.createElement("div",{"data-slot":"full-layout-header-center",className:"flex-1 min-w-0"},n),null!=t&&/* @__PURE__ */e.createElement("div",{"data-slot":"full-layout-header-trigger-right",className:"ml-auto"},/* @__PURE__ */e.createElement("button",{type:"button",onClick:t,className:"p-2 hover:bg-gray-100 rounded-lg transition-colors","aria-label":"Toggle chat"},
19
19
  /* @__PURE__ */e.createElement("svg",{className:"size-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},/* @__PURE__ */e.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"}))))));B.displayName="FullLayoutHeader";export{j as F,B as a,R as b,b as c,w as d,k as e,N as f,y as u};
20
- //# sourceMappingURL=header-pane.eU7f_pim.js.map
20
+ //# sourceMappingURL=header-pane.BBWfo7uv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-pane.BBWfo7uv.js","sources":["../../../src/layouts/full-layout/context.tsx","../../../src/layouts/full-layout/components/main-area.tsx","../../../src/layouts/full-layout/components/body.tsx","../../../src/layouts/full-layout/components/header.tsx","../../../src/layouts/full-layout/left-sidebar-pane.tsx","../../../src/layouts/full-layout/components/left-sidebar.tsx","../../../src/layouts/full-layout/components/main.tsx","../../../src/layouts/full-layout/right-sidebar-pane.tsx","../../../src/layouts/full-layout/components/right-sidebar.tsx","../../../src/layouts/full-layout/blurry-blob.tsx","../../../src/layouts/full-layout/layout.tsx","../../../src/layouts/full-layout/header-pane.tsx"],"sourcesContent":["import React from 'react';\n\n/** Nav state derived from route (e.g. active ids). Provided by Platform; when null, layout uses props only. */\nexport interface FullLayoutNavState {\n appNavActiveId?: string;\n sectionNavActiveId?: string;\n activeArticleId?: string;\n}\n\nconst FullLayoutNavStateContext = React.createContext<FullLayoutNavState | null>(null);\n\nexport { FullLayoutNavStateContext };\n\nexport interface FullLayoutContextValue {\n isSideNavOpen: boolean;\n isRightSidebarOpen: boolean;\n toggleSidebar: () => void;\n openRightSidebar: () => void;\n closeRightSidebar: () => void;\n toggleRightSidebar: () => void;\n showSidenav: () => void;\n disableLeftSideNav: boolean;\n leftNavWidth: number;\n isMobile: boolean;\n sideNavRef: React.RefObject<HTMLDivElement | null>;\n rightSidebarRef: React.RefObject<HTMLElement | null>;\n sidebarToggleRef: React.RefObject<HTMLButtonElement | null>;\n handleResizeStart: (e: React.MouseEvent) => void;\n handleResizeKeyDown: (e: React.KeyboardEvent) => void;\n ariaValuenow: number;\n ariaValuemin: number;\n ariaValuemax: number;\n /** Mirrors `FullLayout` prop; `FullLayout.Main` uses it for viewport height when Radix ScrollArea is off. */\n disableMainScrollArea: boolean;\n}\n\nconst FullLayoutContext = React.createContext<FullLayoutContextValue | null>(null);\n\nexport interface FullLayoutProviderProps {\n value: FullLayoutContextValue;\n children: React.ReactNode;\n}\n\nexport function FullLayoutProvider({ value, children }: FullLayoutProviderProps) {\n return <FullLayoutContext.Provider value={value}>{children}</FullLayoutContext.Provider>;\n}\n\nexport function useFullLayoutContext(): FullLayoutContextValue | null {\n return React.useContext(FullLayoutContext);\n}\n\nexport interface UseRightSidebarReturn {\n /** Whether the right sidebar is currently open. */\n isOpen: boolean;\n /** Open the right sidebar (no-op if already open). */\n open: () => void;\n /** Close the right sidebar (no-op if already closed). */\n close: () => void;\n /** Toggle the right sidebar open/closed. */\n toggle: () => void;\n}\n\n/**\n * Convenience hook for programmatically controlling the right sidebar.\n *\n * Must be used inside a `<FullLayout>` tree.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const rightSidebar = useRightSidebar();\n * return <button onClick={rightSidebar.open}>Open sidebar</button>;\n * }\n * ```\n */\nexport function useRightSidebar(): UseRightSidebarReturn {\n const ctx = React.useContext(FullLayoutContext);\n if (!ctx) {\n throw new Error('useRightSidebar must be used within a <FullLayout> component.');\n }\n\n return {\n isOpen: ctx.isRightSidebarOpen,\n open: ctx.openRightSidebar,\n close: ctx.closeRightSidebar,\n toggle: ctx.toggleRightSidebar,\n };\n}\n","import React from 'react';\n\nexport function MainArea({ children }: { children?: React.ReactNode }) {\n return <>{children}</>;\n}\nMainArea.displayName = 'FullLayout.MainArea';\n","import React from 'react';\n\nexport function Body({ children }: { children?: React.ReactNode }) {\n return <>{children}</>;\n}\nBody.displayName = 'FullLayout.Body';\n","import React, { ComponentProps } from 'react';\n\nimport { HeaderNav } from '../../header-nav';\nimport { useFullLayoutContext } from '../context';\n\nexport function Header(props: ComponentProps<typeof HeaderNav>) {\n const { onBrainfishNativeClick, ...rest } = props;\n const ctx = useFullLayoutContext();\n if (!ctx) return null;\n\n const handleNativeClick = () => {\n ctx.toggleRightSidebar();\n onBrainfishNativeClick?.();\n };\n\n return (\n <HeaderNav\n {...rest}\n showSidebarToggle={!ctx.disableLeftSideNav}\n isSideNavOpen={ctx.isSideNavOpen}\n isRightSidebarOpen={ctx.isRightSidebarOpen}\n onSideNavigationToggle={ctx.toggleSidebar}\n onBrainfishNativeClick={handleNativeClick}\n sidebarToggleRef={ctx.sidebarToggleRef as React.Ref<HTMLButtonElement>}\n />\n );\n}\nHeader.displayName = 'FullLayout.Header';\n","import React, { ComponentProps } from 'react';\n\nimport { Sidebar } from '../sidebar';\n\nimport { cn } from '@/lib/utils';\n\nexport interface FullLayoutLeftSidebarProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'children'> {\n open: boolean;\n widthPx: number;\n onResizeStart: (e: React.MouseEvent) => void;\n onResizeKeyDown: (e: React.KeyboardEvent) => void;\n ariaValuenow: number;\n ariaValuemin: number;\n ariaValuemax: number;\n sidebarProps: ComponentProps<typeof Sidebar>;\n}\n\nconst FullLayoutLeftSidebar = React.forwardRef<HTMLDivElement, FullLayoutLeftSidebarProps>(\n (\n {\n open,\n widthPx,\n onResizeStart,\n onResizeKeyDown,\n ariaValuenow,\n ariaValuemin,\n ariaValuemax,\n sidebarProps,\n className,\n ...props\n },\n ref,\n ) => {\n return (\n <div\n ref={ref}\n id=\"left-sidebar-pane\"\n data-slot=\"full-layout-left-sidebar\"\n tabIndex={-1}\n data-state={open ? 'open' : 'closed'}\n className={cn(\n 'min-w-0 overflow-hidden flex flex-col relative transition-[width] data-[state=closed]:w-0 data-[state=closed]:pointer-events-none data-[state=open]:w-full md:data-[state=open]:w-[var(--left-nav-width)]',\n className,\n )}\n style={{\n ['--left-nav-width' as string]: `${widthPx}px`,\n }}\n {...props}\n >\n <Sidebar\n {...sidebarProps}\n collapsed={!open}\n className=\"transition-[width] w-[var(--left-nav-width)] h-[calc(100dvh-var(--header-nav-height,0))]\"\n />\n\n {/* Window Splitter per APG: https://www.w3.org/ARIA/apg/patterns/windowsplitter/ */}\n {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- focusable separator per APG Window Splitter pattern */}\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-valuenow={ariaValuenow}\n aria-valuemin={ariaValuemin}\n aria-valuemax={ariaValuemax}\n aria-label=\"Resize left sidebar\"\n aria-controls=\"left-sidebar-pane\"\n data-slot=\"full-layout-left-sidebar-resize-handle\"\n /* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- focusable separator per APG Window Splitter pattern */\n tabIndex={0}\n onMouseDown={onResizeStart}\n onKeyDown={onResizeKeyDown}\n className=\"hidden lg:block absolute top-0 right-0 bottom-0 w-1 bg-transparent hover:bg-blue-500 cursor-ew-resize shrink-0 [&>div]:pointer-events-none\"\n >\n <div className=\"absolute inset-y-0 -right-1 w-3\" />\n </div>\n </div>\n );\n },\n);\n\nFullLayoutLeftSidebar.displayName = 'FullLayoutLeftSidebar';\n\nexport { FullLayoutLeftSidebar };\n","import React, { ComponentProps, useContext } from 'react';\n\nimport { Sidebar } from '../../sidebar';\nimport { FullLayoutNavStateContext, useFullLayoutContext } from '../context';\nimport { FullLayoutLeftSidebar } from '../left-sidebar-pane';\n\nexport function LeftSidebar(props: ComponentProps<typeof Sidebar>) {\n const ctx = useFullLayoutContext();\n const navState = useContext(FullLayoutNavStateContext);\n if (!ctx) return null;\n if (ctx.disableLeftSideNav) return null;\n\n const sidebarProps: ComponentProps<typeof Sidebar> =\n navState != null\n ? {\n ...props,\n appNavActiveId: navState.appNavActiveId ?? props.appNavActiveId,\n sectionNavActiveId: navState.sectionNavActiveId ?? props.sectionNavActiveId,\n activeArticleId: navState.activeArticleId ?? props.activeArticleId,\n }\n : props;\n\n return (\n <FullLayoutLeftSidebar\n ref={ctx.sideNavRef as React.Ref<HTMLDivElement>}\n open={ctx.isSideNavOpen}\n widthPx={ctx.leftNavWidth}\n onResizeStart={ctx.handleResizeStart}\n onResizeKeyDown={ctx.handleResizeKeyDown}\n ariaValuenow={ctx.ariaValuenow}\n ariaValuemin={ctx.ariaValuemin}\n ariaValuemax={ctx.ariaValuemax}\n sidebarProps={sidebarProps}\n />\n );\n}\nLeftSidebar.displayName = 'FullLayout.LeftSidebar';\n","import React from 'react';\n\nimport { useFullLayoutContext } from '../context';\n\nimport { Item } from '@/components/ui/item';\nimport { cn } from '@/lib/utils';\n\nexport function Main({ children, className }: { children?: React.ReactNode; className?: string }) {\n const ctx = useFullLayoutContext();\n const mainIsScrollContainer = ctx?.disableMainScrollArea ?? false;\n\n return (\n <Item\n className={cn(\n 'p-0',\n mainIsScrollContainer &&\n 'h-[calc(100dvh-var(--header-nav-height))] md:h-[calc(100dvh-var(--header-nav-height)-1rem)] min-h-0 overflow-y-auto p-4 md:p-8',\n className,\n )}\n >\n {children}\n </Item>\n );\n}\nMain.displayName = 'FullLayout.Main';\n","import React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport { ScrollArea } from '@/components/ui/scroll-area';\n\nexport interface FullLayoutRightSidebarProps extends Omit<React.ComponentPropsWithoutRef<'aside'>, 'children'> {\n open: boolean;\n children: React.ReactNode;\n scrollable?: boolean;\n}\n\nconst FullLayoutRightSidebar = React.forwardRef<HTMLElement, FullLayoutRightSidebarProps>(\n ({ open, scrollable = true, className, children, ...props }, ref) => {\n return (\n <aside\n ref={ref as React.Ref<HTMLDivElement>}\n data-slot=\"full-layout-right-sidebar\"\n data-state={open ? 'open' : 'closed'}\n className={cn(\n 'bg-background shadow-lg flex flex-col',\n 'fixed h-[calc(100dvh-var(--header-nav-height))] z-10 w-screen transition-transform duration-300 ease-in-out data-[state=closed]:translate-x-full data-[state=open]:translate-x-0',\n 'md:relative md:h-dvh md:w-96 md:top-0',\n 'border-dark-300 border-l border-r-0 border-t-0 border-b-0',\n className,\n )}\n {...props}\n >\n <div className=\"flex flex-col flex-1 min-h-0 overflow-hidden\" data-slot=\"full-layout-right-sidebar-content\">\n {scrollable ? (\n <ScrollArea className=\"h-[calc(100dvh-var(--header-nav-height))] md:h-dvh\">{children}</ScrollArea>\n ) : (\n children\n )}\n </div>\n </aside>\n );\n },\n);\n\nFullLayoutRightSidebar.displayName = 'FullLayoutRightSidebar';\n\nexport { FullLayoutRightSidebar };\n","import React from 'react';\n\nimport { useFullLayoutContext } from '../context';\nimport { FullLayoutRightSidebar } from '../right-sidebar-pane';\n\nexport function RightSidebar({ children, scrollable }: { children?: React.ReactNode; scrollable?: boolean }) {\n const ctx = useFullLayoutContext();\n if (!ctx) return null;\n\n return (\n <FullLayoutRightSidebar\n ref={ctx.rightSidebarRef as React.Ref<HTMLElement>}\n open={ctx.isRightSidebarOpen}\n scrollable={scrollable}\n className=\"top-[var(--header-nav-height)]\"\n >\n {children}\n </FullLayoutRightSidebar>\n );\n}\nRightSidebar.displayName = 'FullLayout.RightSidebar';\n","import React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst blurryBlob = cva('blurry-blob', {\n variants: {\n variant: {\n topLeft: '',\n bottomRight: '!top-auto !left-auto !-bottom-[10%] !right-0 rotate-180',\n },\n },\n defaultVariants: {\n variant: 'topLeft',\n },\n});\n\ninterface BlurryBlobProps extends VariantProps<typeof blurryBlob> {\n className?: string;\n}\n\nexport const GooFilter = () => (\n <svg xmlns=\"http://www.w3.org/2000/svg\" className=\"fixed top-0 left-0 w-0 h-0\">\n <defs>\n <filter id=\"goo\">\n <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"10\" result=\"blur\" />\n <feColorMatrix in=\"blur\" mode=\"matrix\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -8\" result=\"goo\" />\n <feBlend in=\"SourceGraphic\" in2=\"goo\" />\n </filter>\n </defs>\n </svg>\n);\n\nconst BlurryBlob = ({ className, variant }: BlurryBlobProps) => {\n return (\n <div className={cn(blurryBlob({ variant }), className)}>\n <div className=\"gradients-container\">\n <div className=\"g1\" />\n <div className=\"g2\" />\n <div className=\"g3\" />\n <div className=\"g4\" />\n </div>\n </div>\n );\n};\n\nexport default BlurryBlob;\n","import React, { useState, useRef, useEffect, useCallback, isValidElement, useMemo } from 'react';\nimport { useMediaQuery } from 'usehooks-ts';\n\nimport { FullLayoutProvider } from './context';\nimport { HEADER_NAV_HEIGHT } from '../header-nav';\nimport * as Compounds from './components';\nimport BlurryBlob, { GooFilter } from './blurry-blob';\n\nimport { ScrollArea } from '@/components/ui/scroll-area';\nimport { FeatureFlagProvider } from '@/lib/feature-flags';\nimport type { FeatureFlags } from '@/lib/feature-flags';\nimport { cn } from '@/lib/utils';\n\nconst MIN_SIDENAV_WIDTH = 260;\nconst CLOSE_SIDENAV_THRESHOLD = MIN_SIDENAV_WIDTH - 32;\nconst MAX_SIDENAV_WIDTH = 600;\nconst RIGHT_SIDEBAR_WIDTH = 384;\n\nexport interface FullLayoutRootProps {\n children?: React.ReactNode;\n disableLeftSideNav?: boolean;\n /** When true, main column uses a plain div (no Radix ScrollArea); viewport height is applied on `FullLayout.Main`. */\n disableMainScrollArea?: boolean;\n className?: string;\n /** When true, the right sidebar is always open and cannot be toggled. */\n forceRightSidebarOpen?: boolean;\n}\n\n// Helper function to extract slots from children\nfunction extractSlots<T extends Record<string, React.ComponentType<any>>>(\n children: React.ReactNode,\n refs: T,\n): { [K in keyof T]: React.ReactElement | null } {\n const out = {} as { [K in keyof T]: React.ReactElement | null };\n const arr = React.Children.toArray(children);\n for (const key of Object.keys(refs) as (keyof T)[]) {\n const match = arr.find((c): c is React.ReactElement => isValidElement(c) && c.type === refs[key]);\n out[key] = match ?? null;\n }\n\n return out;\n}\n\n// --- Root component ---\n\nfunction FullLayoutRoot({\n children,\n disableLeftSideNav = false,\n disableMainScrollArea = false,\n className,\n forceRightSidebarOpen = false,\n}: FullLayoutRootProps) {\n // Match isMobile breakpoint (max-width: 767px) so no flash on 768–1023px\n const [isSideNavOpen, setIsSideNavOpen] = useState(() => typeof window !== 'undefined' && window.innerWidth > 767);\n const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(forceRightSidebarOpen);\n const [isResizing, setIsResizing] = useState(false);\n const [gridWidth, setGridWidth] = useState(() => (typeof window !== 'undefined' ? window.innerWidth : 0));\n\n const newMinNavWidth = useMemo(\n () =>\n gridWidth > 0\n ? Math.max(MIN_SIDENAV_WIDTH, Math.min(MAX_SIDENAV_WIDTH, Math.round(gridWidth * 0.18)))\n : MIN_SIDENAV_WIDTH,\n [gridWidth],\n );\n const [leftNavWidth, setLeftNavWidth] = useState(newMinNavWidth);\n\n const sideNavRef = useRef<HTMLDivElement>(null);\n const rightSidebarRef = useRef<HTMLElement>(null);\n const sidebarToggleRef = useRef<HTMLButtonElement>(null);\n const gridRef = useRef<HTMLDivElement>(null);\n const prevSideNavOpenRef = useRef(isSideNavOpen);\n const resizeAcRef = useRef<AbortController | null>(null);\n const isMobile = useMediaQuery('(max-width: 767px)');\n const isWideDesktop = useMediaQuery('(min-width: 1280px)');\n\n // Prevent native scrollbar from appearing\n useEffect(() => {\n if (!document.body.querySelector('.sbdocs')) {\n document.body.style.overflow = 'hidden';\n }\n\n return () => {\n document.body.style.overflow = '';\n };\n }, []);\n\n // Handle grid width changes\n useEffect(() => {\n const el = gridRef.current;\n if (!el) return;\n\n const ro = new ResizeObserver(([entry]) => {\n setGridWidth(entry.contentRect.width);\n });\n ro.observe(el);\n\n return () => ro.disconnect();\n }, []);\n\n // Close other sidebar if opening a new one on wide desktop\n const closeOtherSidebar = useCallback(\n (opening: 'left' | 'right') => {\n if (isWideDesktop) return;\n if (opening === 'left') setIsRightSidebarOpen(false);\n else setIsSideNavOpen(false);\n },\n [isWideDesktop],\n );\n\n // Show left sidebar\n const showSidenav = useCallback(() => {\n closeOtherSidebar('left');\n setIsSideNavOpen(true);\n setLeftNavWidth(newMinNavWidth);\n sideNavRef.current?.focus();\n }, [closeOtherSidebar, newMinNavWidth]);\n\n // Toggle left sidebar\n const toggleSidebar = useCallback(() => {\n closeOtherSidebar('left');\n setIsSideNavOpen((prev) => !prev);\n }, [closeOtherSidebar]);\n\n // Open right sidebar (no-op if already open)\n const openRightSidebar = useCallback(() => {\n closeOtherSidebar('right');\n setIsRightSidebarOpen(true);\n }, [closeOtherSidebar]);\n\n // Close right sidebar (no-op if already closed)\n const closeRightSidebar = useCallback(() => {\n setIsRightSidebarOpen(false);\n }, []);\n\n // Toggle right sidebar\n const toggleRightSidebar = useCallback(() => {\n if (forceRightSidebarOpen) return;\n closeOtherSidebar('right');\n setIsRightSidebarOpen((prev) => !prev);\n }, [closeOtherSidebar, forceRightSidebarOpen]);\n\n // Apply left nav width - used for resizing left sidebar\n const applyLeftNavWidth = useCallback(\n (newWidth: number): boolean => {\n if (newWidth < CLOSE_SIDENAV_THRESHOLD) {\n setLeftNavWidth(newMinNavWidth);\n setIsSideNavOpen(false);\n\n return true;\n } else {\n // clamp to MAX_SIDENAV_WIDTH\n setLeftNavWidth(Math.min(MAX_SIDENAV_WIDTH, newWidth));\n\n return false;\n }\n },\n [newMinNavWidth],\n );\n\n // Handle resize start\n const handleResizeStart = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n resizeAcRef.current = new AbortController();\n setIsResizing(true);\n }, []);\n\n // Handle resize end\n const endResize = useCallback(() => {\n setIsResizing(false);\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n }, []);\n\n // Handle mouse move during resize\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!isResizing) return;\n if (applyLeftNavWidth(e.clientX)) {\n endResize();\n }\n },\n [isResizing, applyLeftNavWidth, endResize],\n );\n\n // Handle mouse up during resize\n const handleMouseUp = useCallback(() => endResize(), [endResize]);\n\n // Set inert attribute on side nav and right sidebar - disables tabbing to elements inside when sidebar is closed\n const setInert = useCallback((el: HTMLElement | null, open: boolean) => {\n if (!el) return;\n if (open) el.removeAttribute('inert');\n else el.setAttribute('inert', '');\n }, []);\n\n useEffect(() => {\n setInert(sideNavRef.current, isSideNavOpen);\n setInert(rightSidebarRef.current, isRightSidebarOpen);\n }, [isSideNavOpen, isRightSidebarOpen]);\n\n // Attach resize listeners when isResizing becomes true (controller already set in handleResizeStart)\n useEffect(() => {\n if (!isResizing || !resizeAcRef.current) return;\n\n const { signal } = resizeAcRef.current;\n document.body.style.cursor = 'ew-resize';\n document.body.style.userSelect = 'none';\n document.addEventListener('mousemove', handleMouseMove, { signal });\n document.addEventListener('mouseup', handleMouseUp, { signal });\n window.addEventListener('mouseup', handleMouseUp, { signal });\n setInert(sideNavRef.current, false);\n\n return () => {\n resizeAcRef.current?.abort();\n resizeAcRef.current = null;\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n setInert(sideNavRef.current, true);\n };\n }, [isResizing]);\n\n // Handle resize key down\n const handleResizeKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n const step = 16;\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n applyLeftNavWidth(leftNavWidth - step);\n } else if (e.key === 'ArrowRight') {\n e.preventDefault();\n applyLeftNavWidth(Math.min(MAX_SIDENAV_WIDTH, leftNavWidth + step));\n }\n },\n [leftNavWidth, applyLeftNavWidth],\n );\n\n const leftCol = disableLeftSideNav ? 0 : isSideNavOpen ? (isMobile ? gridWidth : leftNavWidth) : 0;\n const rightCol = isMobile ? 0 : isRightSidebarOpen ? RIGHT_SIDEBAR_WIDTH : 0;\n\n // Set left sidebar open state based on mobile\n useEffect(() => {\n setIsSideNavOpen(isMobile ? false : true);\n }, [isMobile]);\n\n // Focus sidebar toggle when left sidebar is closed\n useEffect(() => {\n if (prevSideNavOpenRef.current && !isSideNavOpen) {\n sidebarToggleRef.current?.focus();\n }\n prevSideNavOpenRef.current = isSideNavOpen;\n }, [isSideNavOpen]);\n\n const ariaRange = MAX_SIDENAV_WIDTH - newMinNavWidth;\n const ariaValuenow = ariaRange === 0 ? 100 : Math.round(((leftNavWidth - newMinNavWidth) / ariaRange) * 100);\n\n const layoutContextValue = useMemo(\n () => ({\n isSideNavOpen,\n isRightSidebarOpen,\n toggleSidebar,\n openRightSidebar,\n closeRightSidebar,\n toggleRightSidebar,\n showSidenav,\n disableLeftSideNav: disableLeftSideNav ?? false,\n leftNavWidth,\n isMobile,\n sideNavRef,\n rightSidebarRef,\n sidebarToggleRef,\n handleResizeStart,\n handleResizeKeyDown,\n ariaValuenow,\n ariaValuemin: 0,\n ariaValuemax: 100,\n disableMainScrollArea,\n }),\n [\n isSideNavOpen,\n isRightSidebarOpen,\n toggleSidebar,\n openRightSidebar,\n closeRightSidebar,\n toggleRightSidebar,\n showSidenav,\n leftNavWidth,\n isMobile,\n disableLeftSideNav,\n disableMainScrollArea,\n handleResizeStart,\n handleResizeKeyDown,\n ariaValuenow,\n ],\n );\n\n // --- Extract slots from children ---\n const { MainArea: mainArea, RightSidebar: rightSidebar } = extractSlots(children, {\n MainArea: Compounds.MainArea,\n RightSidebar: Compounds.RightSidebar,\n });\n const mainAreaChildren = isValidElement(mainArea)\n ? (mainArea.props as { children?: React.ReactNode }).children\n : null;\n const { Header: header, Body: body } = extractSlots(mainAreaChildren ?? null, {\n Header: Compounds.Header,\n Body: Compounds.Body,\n });\n const bodyChildren = isValidElement(body) ? (body.props as { children?: React.ReactNode }).children : null;\n const { LeftSidebar: leftSidebar, Main: main } = extractSlots(bodyChildren ?? null, {\n LeftSidebar: Compounds.LeftSidebar,\n Main: Compounds.Main,\n });\n\n const hasRightSidebar = rightSidebar != null;\n const mainPanelClasses =\n 'md:shadow-[2px_4px_12px_0_rgba(0,0,0,0.14)] md:dark:shadow-[2px_4px_12px_0_rgba(255,255,255,0.14)] md:rounded-lg';\n\n // --- Render the layout ---\n return (\n <FullLayoutProvider value={layoutContextValue}>\n <GooFilter />\n <BlurryBlob variant=\"topLeft\" />\n <BlurryBlob variant=\"bottomRight\" />\n <div\n className={cn(\n 'min-h-screen w-screen bg-background grid transition-[grid-template-columns] duration-300 ease-in-out',\n className,\n )}\n style={{\n gridTemplateColumns: hasRightSidebar ? 'minmax(0, 1fr) var(--right-col)' : 'minmax(0, 1fr)',\n ['--right-col' as string]: hasRightSidebar ? `${rightCol}px` : '0px',\n ['--header-nav-height' as string]: `${HEADER_NAV_HEIGHT}px`,\n }}\n >\n <div className=\"min-w-0 grid grid-rows-[auto_1fr]\">\n {header}\n\n <div\n ref={gridRef}\n data-sidenav-state={isSideNavOpen ? 'open' : 'closed'}\n data-right-sidebar-state={isRightSidebarOpen ? 'open' : 'closed'}\n className={cn(\n 'group/sidenav group/rightsidebar min-h-0 min-w-0 grid transition-[grid-template-columns] ease-linear',\n )}\n style={{\n gridTemplateColumns: `var(--left-col) minmax(0, 1fr)`,\n ['--left-col' as string]: `${leftCol}px`,\n }}\n >\n {disableLeftSideNav ? <div className=\"w-0\" /> : leftSidebar}\n <div className={cn('relative md:mr-4', { 'md:mx-4': !isSideNavOpen || disableLeftSideNav })}>\n {disableMainScrollArea ? (\n <div className={cn('bg-surface', mainPanelClasses)}>\n <main className=\"min-w-0 relative\">{main}</main>\n </div>\n ) : (\n <>\n <ScrollArea\n className={cn(\n 'bg-surface h-[calc(100dvh-var(--header-nav-height))] md:h-[calc(100dvh-var(--header-nav-height)-1rem)]',\n mainPanelClasses,\n )}\n >\n <div className=\"min-h-full\">\n <main className={cn('min-w-0 overflow-visible relative p-4 md:p-8')}>{main}</main>\n </div>\n </ScrollArea>\n <div\n aria-hidden\n className=\"main-scroll-fade pointer-events-none sticky bottom-4 left-0 right-0 h-16 bg-gradient-to-t from-surface to-transparent md:rounded-b-lg\"\n />\n </>\n )}\n </div>\n </div>\n </div>\n\n {hasRightSidebar ? rightSidebar : null}\n </div>\n </FullLayoutProvider>\n );\n}\n\n/**\n * Custom memo comparator: re-render only when these props change by reference/value.\n * Use this so changing other props (e.g. from a parent) does not force the layout shell to re-render.\n */\nfunction fullLayoutRootPropsAreEqual(prev: FullLayoutRootProps, next: FullLayoutRootProps): boolean {\n return (\n prev.children === next.children &&\n prev.className === next.className &&\n prev.disableLeftSideNav === next.disableLeftSideNav &&\n prev.disableMainScrollArea === next.disableMainScrollArea\n );\n}\n\nconst FullLayoutRootMemo = React.memo(FullLayoutRoot, fullLayoutRootPropsAreEqual);\n\nexport interface FullLayoutProps extends FullLayoutRootProps {\n /** Feature flags for layout/header (e.g. platform.brainfish-native.enabled). Passed through to FeatureFlagProvider. */\n featureFlags?: FeatureFlags;\n}\n\nfunction FullLayoutWithFlags(props: FullLayoutProps) {\n const { featureFlags, ...layoutProps } = props;\n\n return (\n <FeatureFlagProvider flags={featureFlags}>\n <FullLayoutRootMemo {...layoutProps} />\n </FeatureFlagProvider>\n );\n}\n\ntype FullLayoutCompound = typeof FullLayoutWithFlags & {\n MainArea: typeof Compounds.MainArea;\n Body: typeof Compounds.Body;\n Header: typeof Compounds.Header;\n LeftSidebar: typeof Compounds.LeftSidebar;\n Main: typeof Compounds.Main;\n RightSidebar: typeof Compounds.RightSidebar;\n};\n\nconst FullLayout: FullLayoutCompound = Object.assign(FullLayoutWithFlags, {\n MainArea: Compounds.MainArea,\n Body: Compounds.Body,\n Header: Compounds.Header,\n LeftSidebar: Compounds.LeftSidebar,\n Main: Compounds.Main,\n RightSidebar: Compounds.RightSidebar,\n});\n\nexport default FullLayout;\n","import React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nexport interface FullLayoutHeaderProps extends React.ComponentPropsWithoutRef<'header'> {\n onOpenLeft?: () => void;\n onOpenRight?: () => void;\n}\n\nconst FullLayoutHeader = React.forwardRef<HTMLElement, FullLayoutHeaderProps>(\n ({ onOpenLeft, onOpenRight, className, children, ...props }, ref) => {\n return (\n <header\n ref={ref as React.Ref<HTMLHeadingElement>}\n data-slot=\"full-layout-header\"\n className={cn('h-16 bg-white shadow-md flex items-center px-4 shrink-0 sticky top-0 z-20', className)}\n {...props}\n >\n {onOpenLeft != null && (\n <button\n data-slot=\"full-layout-header-trigger-left\"\n type=\"button\"\n onClick={onOpenLeft}\n className=\"p-2 hover:bg-gray-100 rounded-lg transition-colors mr-4 lg:hidden\"\n aria-label=\"Toggle menu\"\n >\n <svg className=\"size-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 6h16M4 12h16M4 18h16\" />\n </svg>\n </button>\n )}\n <div data-slot=\"full-layout-header-center\" className=\"flex-1 min-w-0\">\n {children}\n </div>\n {onOpenRight != null && (\n <div data-slot=\"full-layout-header-trigger-right\" className=\"ml-auto\">\n <button\n type=\"button\"\n onClick={onOpenRight}\n className=\"p-2 hover:bg-gray-100 rounded-lg transition-colors\"\n aria-label=\"Toggle chat\"\n >\n <svg className=\"size-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z\"\n />\n </svg>\n </button>\n </div>\n )}\n </header>\n );\n },\n);\n\nFullLayoutHeader.displayName = 'FullLayoutHeader';\n\nexport { FullLayoutHeader };\n"],"names":["FullLayoutNavStateContext","React","createContext","FullLayoutContext","FullLayoutProvider","value","children","createElement","Provider","useFullLayoutContext","useContext","useRightSidebar","ctx","Error","isOpen","isRightSidebarOpen","open","openRightSidebar","close","closeRightSidebar","toggle","toggleRightSidebar","MainArea","Body","Header","props","onBrainfishNativeClick","rest","HeaderNav","showSidebarToggle","disableLeftSideNav","isSideNavOpen","onSideNavigationToggle","toggleSidebar","sidebarToggleRef","displayName","FullLayoutLeftSidebar","forwardRef","widthPx","onResizeStart","onResizeKeyDown","ariaValuenow","ariaValuemin","ariaValuemax","sidebarProps","className","ref","id","tabIndex","cn","style","Sidebar","collapsed","role","onMouseDown","onKeyDown","LeftSidebar","navState","appNavActiveId","sectionNavActiveId","activeArticleId","sideNavRef","leftNavWidth","handleResizeStart","handleResizeKeyDown","Main","mainIsScrollContainer","disableMainScrollArea","Item","FullLayoutRightSidebar","scrollable","ScrollArea","RightSidebar","rightSidebarRef","blurryBlob","cva","variants","variant","topLeft","bottomRight","defaultVariants","GooFilter","xmlns","in","stdDeviation","result","mode","values","in2","BlurryBlob","MAX_SIDENAV_WIDTH","extractSlots","refs","out","arr","Children","toArray","key","Object","keys","match","find","c","isValidElement","type","FullLayoutRootMemo","memo","forceRightSidebarOpen","setIsSideNavOpen","useState","window","innerWidth","setIsRightSidebarOpen","isResizing","setIsResizing","gridWidth","setGridWidth","newMinNavWidth","useMemo","Math","max","min","round","setLeftNavWidth","useRef","gridRef","prevSideNavOpenRef","resizeAcRef","isMobile","useMediaQuery","isWideDesktop","useEffect","document","body","querySelector","overflow","el","current","ro","ResizeObserver","entry","contentRect","width","observe","disconnect","closeOtherSidebar","useCallback","opening","showSidenav","focus","prev","applyLeftNavWidth","newWidth","MIN_SIDENAV_WIDTH","e","preventDefault","AbortController","endResize","cursor","userSelect","handleMouseMove","clientX","handleMouseUp","setInert","removeAttribute","setAttribute","signal","addEventListener","abort","leftCol","rightCol","ariaRange","layoutContextValue","mainArea","rightSidebar","Compounds.MainArea","Compounds.RightSidebar","mainAreaChildren","header","Compounds.Header","Compounds.Body","bodyChildren","leftSidebar","main","Compounds.LeftSidebar","Compounds.Main","hasRightSidebar","mainPanelClasses","gridTemplateColumns","HEADER_NAV_HEIGHT","Fragment","next","FullLayout","assign","featureFlags","layoutProps","FeatureFlagProvider","flags","FullLayoutHeader","onOpenLeft","onOpenRight","onClick","fill","stroke","viewBox","strokeLinecap","strokeLinejoin","strokeWidth","d"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import*as e from"react";var t=Object.defineProperty,n=(e,n)=>t(e,"name",{value:n,configurable:!0});function r(e,t){if("function"==typeof e)return e(t);null!=e&&(e.current=t)}function o(...e){return t=>{let n=!1;const o=e.map(e=>{const o=r(e,t);return n||"function"!=typeof o||(n=!0),o});if(n)return()=>{for(let t=0;t<o.length;t++){const n=o[t];"function"==typeof n?n():r(e[t],null)}}}}function l(...t){return e.useCallback(o(...t),t)}n(r,"setRef"),n(o,"composeRefs"),n(l,"useComposedRefs");var i=Object.defineProperty,c=(e,t)=>i(e,"name",{value:t,configurable:!0});function a(t){const n=e.forwardRef((n,r)=>{let{children:o,...i}=n,c=null,a=!1;const s=[];h(o)&&"function"==typeof E&&(o=E(o._payload)),e.Children.forEach(o,e=>{if(m(e)){a=!0;const t=e;let n="child"in t.props?t.props.child:t.props.children;h(n)&&"function"==typeof E&&(n=E(n._payload)),c=f(t,n),s.push(c?.props?.children)}else s.push(e)}),c?c=e.cloneElement(c,void 0,s):!a&&1===e.Children.count(o)&&e.isValidElement(o)&&(c=o);const p=c?d(c):void 0,y=l(r,p);if(!c){if(o||0===o)throw new Error(a?S(t):g(t));return o}const b=u(i,c.props??{});return c.type!==e.Fragment&&(b.ref=r?y:p),e.cloneElement(c,b)});return n.displayName=`${t}.Slot`,n}c(a,"createSlot");var s=/* @__PURE__ */a("Slot"),p=Symbol.for("radix.slottable");c(function(e){const t=/* @__PURE__ */c(e=>"child"in e?e.children(e.child):e.children,"Slottable");return t.displayName=`${e}.Slottable`,t.__radixId=p,t},"createSlottable");var f=/* @__PURE__ */c((t,n)=>{if("child"in t.props){const n=t.props.child;return e.isValidElement(n)?e.cloneElement(n,void 0,t.props.children(n.props.children)):null}return e.isValidElement(n)?n:null},"getSlottableElementFromSlottable");function u(e,t){const n={...t};for(const r in t){const o=e[r],l=t[r];/^on[A-Z]/.test(r)?o&&l?n[r]=(...e)=>{const t=l(...e);return o(...e),t}:o&&(n[r]=o):"style"===r?n[r]={...o,...l}:"className"===r&&(n[r]=[o,l].filter(Boolean).join(" "))}return{...e,...n}}function d(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}function m(t){return e.isValidElement(t)&&"function"==typeof t.type&&"__radixId"in t.type&&t.type.__radixId===p}c(u,"mergeProps"),c(d,"getElementRef"),c(m,"isSlottable");var y=Symbol.for("react.lazy");function h(e){return null!=e&&"object"==typeof e&&"$$typeof"in e&&e.$$typeof===y&&"_payload"in e&&b(e._payload)}function b(e){return"object"==typeof e&&null!==e&&"then"in e}c(h,"isLazyComponent"),c(b,"isPromiseLike");var g=/* @__PURE__ */c(e=>`${e} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`,"createSlotError"),S=/* @__PURE__ */c(e=>`${e} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`,"createSlottableError"),E=e[" use ".trim().toString()];export{s as S};
2
+ //# sourceMappingURL=index.Ca2JkOFO.js.map